Hacker News new | ask | show | jobs
by sauere 3801 days ago
Funny, everything in this post is exactly why i prefer Python(+Django/Flask) over Ruby(+Rails). Too much magic happening everywhere, a gazillion of built-in methods, weird shit happening all over the place, all that combined with all the syntactic sugar Ruby offers.

Granted, it might look "beautiful" in the eyes of a experienced RoR developer, but personally i find it just makes code very hard to read. Just my 2 cents.

14 comments

This is I think the biggest reason Rails has remained a pro development tool and has failed to usurp PHP in the beginner realm.

Rails took me about a year of full time work before I could reliably fix bugs in a 1-day timeframe. Before that there were frequent unpredictable week-long goose chases through the Rails stack... usually just to learn I was Doing It Wrong and there was some convention you just really can't break without everything falling apart.

I had the same experience with Ember.js, another convention-over-configuration framework. A year of full time development before I could bang stuff out without being afraid I'd get totally lost and have a feature balloon up from two days to a month. And this is with a computer science degree and 10 years of web development experience.

Once you get past that first year, it's great. Or, if you're on a team of people, you just get help.

I taught RailsBridge a few times, and it's kind of crazy how many concepts you have to teach someone just to get to the point of a simple web form on Rails. It's probably at least 100 totally disconnected ideas, from routers to HTML to templating engines to MVC.

We're just not doing a good job paving a path from PHP's functional, minimalist, "copy/paste this snippet of code into your template" architecture to things like Rails/Ember which provide ergonomic toolkits for modern apps.

You are so right and it breaks my heart every time I think about it.

I just don't know what could really bridge that gap. So many terrible php and <insert language you like to make fun of> codebases come from exactly

> "copy/paste this snippet of code into your template"

programming.

Which is how most beginners learn (myself included. cobbling together myspace and wordpress templates) and it takes a while before you've done enough of it to see how deep the holes you dig with it can be but in the mean time your stuff "works" but is a nightmare to maintain and understand long term.

I had a lot of hope for the node community (because at least then newbies only have to learn the quirks of one scripting language) solving this with express/ sails or something else but they've stalled.

I guess its just a hard problem. No idea how to solve it

>>Which is how most beginners learn (myself included. cobbling together myspace and wordpress templates)

That right there is the problem: too many people are after quick wins and instant gratification and aren't willing to make an effort to learn something the proper way and appreciate its complexity, even if they are not exposed to it directly.

I learned Rails via the Rails Tutorial[1], and finished it in three weeks of evening and weekend sessions. When I started, I knew a little JavaScript, but not much else. By the end of the tutorial, I had a fully functioning app (parts of it I had customized, even!) and was ready to start building my own stuff. It was very empowering and I was blown away by how incredibly smooth and easy the Rails framework was.

That said, if I had taken the approach that lazy PHP beginners take and tried to learn by copy/pasting stuff (or relied on Rails scaffolds from the get-go), I would have stumbled over and over and probably given up.

[1]https://www.railstutorial.org/book

edit: not sure why people are downvoting -- did I say something controversial?

>> Which is how most beginners learn (myself included. cobbling together myspace and wordpress templates) > That right there is the problem: too many people are after quick wins and instant gratification and aren't willing to make an effort to learn something the proper way and appreciate its complexity, even if they are not exposed to it directly.

While I kinda agree with you to a certain degree, I think instant gratification is crucially important, at least in the first "play" phase. A lot of people don't want to know something before they even attempt to play with it. That's what playing is there for, it gives you a small insight of what to expect.

You don't really have a play phase with Rails... or Ruby for that matter, a lot of my front-end or PHP developer friends can't even install Ruby on Windows because it requires you to know certain things about the OS, the language and some very basic things from how linking, compiling and C works (most developers don't).

To even get started with Rails you either need to be a experienced Ruby developer, or dedicate time to learning Rails before you even begin to use it (and even then, I am not sure you could grasp some concepts without knowing Ruby's rich syntax).

Not to mention, you need to know how to use bundler, setup a database, a web server, etc. Compared to most PHP frameworks, where it's just download X, visit /, "voila," edit file something.php, visit `/something` and "voila," again.

Also, as someone who works for a hosting company, most of our PHP developer clients don't RTFM, they prefer blog posts, or YouTube tutorials (this isn't even a joke) because they don't have time for getting started guides. :|

I'm sure you just meant to highlight that reading tutorials is often a better experience than just messing around and hoping to get somewhere (which is why I didn't downvote you), but on first reading it comes across as "if you can't commit to devoting nights and weekends to systematically learn how to code, you're doing it wrong and you're probably a bit of an idiot – like all of those PHP script kiddies."
I think something like Flask is super easy to learn. Go one step further and use Bottle, its a single Python file and its API is one page long.

Its basic, but that should be the point. You can fluidly graduate from Bottle to Flask + extensions or to Django depending on which you prefer. Or just start in Flask - the base API is pretty much the same, its just it has a ton of extensions to implement all the server functionality you could want.

It's an act of balance. You shouldn't have to spend days hunting down simple bugs or figure a convention out. It's not that Rails is bad, but all conventions should be well documented, and if all conventions and general usecases for them cannot fit in a single volume book, it needs to be trimmed down.
You are comparing high-level frameworks like Rails or Ember with PHP, which is a language. Learning ruby is not that harder than learning php (actually it was easier IMHO). And if you compare Symfony or Laravel, and Rails, then again the difference in learning curve is not that big, it takes (quite a few) months to really learn your way around in any of them.
I mean... you don't start out using power tools, right?

Every tool can't be everything to everybody.

At the beginning of my career, there's no way I could do Rails, but nowadays, I'm just not interested in anything but.

I'm not sure what you mean with pro tool.

All those vague concepts kept me away from learning rails because it always felt it was hacked together.

There are also a lot of PHP frameworks that have this pro status because they are hard to learn. But using comments as programming features doesn't sound pro to me.

Some time ago I jumpt fresh into C# and .NET MVC. Now that is what I call a pro framework. It was very easy to learn but so powerfull.

Ofcourse I'm biased now because I fell a little in love with C#

Good tools don't do this to you.
Every bit of that "magic" is either meta programming or a dsl.

A large part of both rails and ruby is very well documented to the point where you can click on methods and see the code that makes them work.

When that fails you can debug with pry.

This stuff is not magic. Its not havening to reinvent the wheel poorly again and again.

The strongest sign someone hasnt spent much time building even a trivial app in ruby with rails is complaints of magic.

The magic straight forword once you see there is just code that generates methods (meta programming)

...

It wasn't until after spending non-trivial amounts of time building a non-trivial app in rails that I realized I hated the magic.

It's not like I don't know that "magic" is metaprogramming and DSLs.

I hate the metaprogramming and DSLs.

  It wasn't until after spending non-trivial amounts of time 
  building a non-trivial app in rails that I realized I hated 
  the magic.
And it wasn't until after spending a non-trivial amount of time building many non-trivial apps in rails that I realized I sincerely appreciate the legwork that Rails saves me from doing. And having worked with rails for a non-trivial amount of time, nearly all of the 'magic' has been dispelled and replaced by an understanding of what Rails does and why. Giving me metaprogrammed finder methods or metaprogrammed getters and setters is wonderful. Maybe you don't like it, but I do, and this is a matter of opinions so the fact that it metaprograms is not a point of critique against the framework.

Telling us you hate metaprogramming does nothing beyond riling up more of a bickerwar.

Yet isn't the time saved by using Rails, the same thing that every other framework offers---without using large amounts of meta-programming?

I mean I like Django. I think its saves me a lot of time. I love the ecosystem, that lets me pick up someone else's apps and use it directly or use it as template to write my own.

Django makes different choices when it comes to what level of metaprogramming to afford, and how to display that to the user.

Now when I first started with MVC on the web (I'd known about it via Smalltalk, but it has a whole different feel there), I used Symfony PHP with its scaffolding and at then point active-record style generators. I liked it, and it helped me understand the core of MVC.

After that, I became opinionated and as my opinions didn't align with that of Symfony or Rails, I chose Python and Django. It was a breath of fresh air because when everything is explicitly defined, it becomes imminently simple to swap out and rewrite things for your given use case.

In fact, the only thing that's pretty difficult to change in Django is the request/response cycle since that's too deeply backed into all the middleware and top level binding to the server.

I've developed non-trivial applications with both Rails and Django and while I agree that Django is more explicit, its overuse of class inheritance made it difficult to figure out what was going on, and made reference tools like this necessary: https://ccbv.co.uk/

I know you don't have to use class-based views and can just use functions with mixins instead, and that's the approach I prefer when working with Django. But if everyone else uses CBVs I still need to understand them.

Rails controllers use inheritance too, but they mostly just inherit directly from ActionController::Base or from ApplicationController. It's not a deeply nested hierarchy.

Hmmm

When it come to their database model I didn't find their use of class-based inheritance to be too crazy. There's some meta-data hacking going on in there; but it wasn't too difficult to figure it out if you wanted to extend the Django ORM to interface with other databases.

In my case, I made an interface to read from ElasticSearch and return Django objects, and store back to ElasticSearch using regular Django objects as well. Since I wasn't creating/restoring everything to ES, my library had to create a partial model that'd look up the rest of the information in Postgres if you touched a field that hadn't been stored in ElasticSearch.

On the view side though, its not too complex when it comes to request objects, response objects, templates and middleware. Injecting new template engines is fairly straight forward, and the request/response objects are just data objects.

However, CBVs are horrible. I never used them. I begun using Django before them, and when they were introduced I tried using it for a month then dropped it entirely. I can see how they can be useful for a CRUD app, or some kind of application where there's a lot of code-reuse going on between the functions related to a particular object, but that's never been the case for me.

I just use functions with decorators for permission & object instantiation from the URL.

Django actually hides all of its ugly internals in their form classes.

Hardly anyone talks about that because for 90% of the use cases it just works, but the moment you want your form widgets to do something complex, you have to write the forms yourself---in their entirety.

I hear that Django 1.9/1.10 is going to give us templates for forms and refactor that code into something sensible. Hopefully it'll be good; but if its a debacle like CBV, I'll just continue doing form templating by hand and using Form only for validation and sanitization.

>Maybe you don't like it, but I do, and this is a matter of opinions so the fact that it metaprograms is not a point of critique against the framework.

Almost every 'critique' of a language is because people don't like the features used/not-used.

It's like saying that the syntax is not a valid critique of brainfuck because you like it.

From someone who loves RSpec along with its metaprogramming and DSLs, I agree that much of "magic" in Rails and its ecosystem is annoying. RSpec gets it right in that it implements an elegant, readable DSL for a specific use case: testing. Rails does a good job at enabling rapid application development but it does a poor job at maintainability and testability. It's a trade-off, but one that I'm less willing to make for complex, long-term applications.
it does a poor job at maintainability and testability

How do you figure?

Just look at the article:

> Progress over stability

> We have to dare occasionally break and change how things are to evolve and grow.

DHH has also revolted against TDD so it's no surprise that conventional Rails apps are hard to test.

DDH was against TDD, but not against testing.

http://david.heinemeierhansson.com/2014/tdd-is-dead-long-liv...

Please don't let Ruby's leaky dynamic action-at-a-distance shoddy excuse for metaprogramming and DSLs scare you away from real metaprogramming and DSLs.

Real metaprogramming doesn't involve action-at-a-distance and flaky dynamic interpretation. Real DSLs don't let you "peek behind the curtain" and therefore don't leak. True metaprogramming systems and DSLs don't let you flout "convention" and get into trouble as RoR does.

Introspection and a dynamic unstratified language do not make a metaprogramming system. High-level well-founded language manipulation constructs do.

Conventions and overloaded operators do not make a DSL. A well-defined grammar and interpreter do.

See OCaml's module system for an example of metaprogramming done right. It is simple to use and gives strong static guarantees. If it compiles, it will run without crashing; else the compiler will give clear error messages if it's unhappy.

See YACC, SQL, and XPath for examples of DSLs done right (albeit arguably abtrusely). They do not leak by design, since they do not let you "peek behind the curtain" which is the #1 way to get yourself (or others) in trouble with DSLs.

> Every bit of that "magic" is either meta programming or a dsl.

No it's not. The boot process hooks up a lot of things without your knowledge. It's an unbounded mass of side-effects that are caused by your code that you have zero path to understand or debug, other than pouring over docs and StackOverflow questions until you understand Rails architecture deeply enough to form a hypothesis about what part of your code might be implicated in the behavior of your app.

DSLs and meta-programming are not what people are talking about when they talk about "magic". They're talking about side effects.

> Too much magic happening everywhere, a gazillion of built-in methods, weird shit happening all over the place

Couldn't agree more on this. Magic is good if you understand the underlying stuff but if you don't and you are once offtrack you are lost. Maybe it's also a matter of taste. However the hype and traction DHH could build when RoR started was quite impressive.

I think this is wrong. Nearly all of us build on top of things we don't understand. I don't understand the physics of a harddisk, or the pipelining of a CPU, or some of the low level OS functionality.

The issue is when do each of us, as individuals and/or teams, really need to understand more. And perhaps more importantly, when does it make business sense to leverage magic to get to market faster, build cheaper, or find market fit before worrying about building the most robust thing possible.

I think this concern gets lost in translation sometimes. It's not that you need to understand everything but you do need to know that there is a layer underneath. And it needs to be possible to follow a trail to and through that layer to debug things. Most of the time I don't need to understand the physics of a harddisk. But I know a hard disk operates under certain pysical laws. When I encounter something happening wrong it has on occasion been useful to be able to follow the trail all the way down to that disk and debug at that level.

Ruby's freewheeling philosophy however means that even when you are the author of the code some other code might have reached in and changed something out from under you. Not only do you not know there is another layer somewhere doing stuff that impacts you you can't even reliably follow a path to find that layer and debug it. As long as everyone does everything perfectly this is a wonderful world to live in. But the first time someone breaks the rules and impacts you and you lose a week or more unnecessarily you'll understand the distaste that ruby fosters in some people.

It's more about being able to discover what you need to know that it is possessing full knowledge of everything you need to know.

> Most of the time I don't need to understand the physics of a harddisk. But I know a hard disk operates under certain physical laws. When I encounter something happening wrong it has on occasion been useful to be able to follow the trail all the way down to that disk and debug at that level.

I'm not entirely sure how a filesystem works under the hood (let alone the physical media it's on), despite interacting heavily with one every day. A bug involving a hard disk would be incredibly opaque to debug, from my perspective. I suspect that you find it easy, because you've spent the time developing an understanding of the system.

> Not only do you not know there is another layer somewhere doing stuff that impacts you you can't even reliably follow a path to find that layer and debug it.

And this is where our roles are reversed! I would have little trouble following that path, all the way down into C if needed- because I've spent plenty of time understanding the system.

The problems you describe are problems anyone would have with an unfamiliar system, and it's not Ruby's fault.

>The problems you describe are problems anyone would have with an unfamiliar system, and it's not Ruby's fault.

Way to reduce what he's saying into the classic, "He can't be depressed! There's starving kids in Africa dying!". You've taken his /observations/, misplaced the usefulness of those observations, and then reduced them into some absurd argument that he's not actually making.

Accessibility to the unfamiliar access goes wonders and reduces the "magic" feel. If ruby doesn't have that sort of accessibility, then it IS Ruby's fault. If that's what Ruby actually wants, that's fine - but holy crap, it's this sort of mentality that completely fucks over usability.

Exactly.

I actually like Ruby, but hate RoR, because of a very lacking sense of control, unless you are very deep into it. You have absolutely zero feeling for what it OK and what's not OK to do with so much implicit magic behind the scenes. And that's debilitating.

My interaction with RoR can be boiled down to this exaggerated dialog:

- Here, write your code in this pattern and it will work.

- And if I change it like this?

- No it won't work.

- But why?

- It's an opinionated framework. It's the opinion of some Very Smart People(tm).

- But this is my work. Why can't I do it the way I want?

- Because you are dumb. And your dog is dumb. You haven't grokked it. You aren't a hacker. Neither is your dog. Where are your hoodie and Nike Airs?

> I don't understand the physics of a harddisk, or the pipelining of a CPU, or some of the low level OS functionality.

I think the key difference is that you don't need to know how that works. I think there's a question of how leaky the abstraction is and how often you need to look behind the curtain.

Tons of people and use cases never need to delve into the implementation to get a lot of mileage out of Rails. I get a lot of work as a Rails consultant and most of the real pain I see is caused by people doing stuff like implementing their own ORM with an ActiveRecord-ish interface, or writing their own caching logic implemented as a mixin for ActiveRecord.
> I think this is wrong. Nearly all of us build on top of things we don't understand. I don't understand the physics of a harddisk, or the pipelining of a CPU, or some of the low level OS functionality.

There's probably a SE paper somewhere stating this formally, but I figure well done implementations on top of abstractions only need to reference one layer down. It might be okay to inspect the state the layer below for performance reasons, but no further.

That means that a well done Rails app might want to inquire about the state of HTTP, but shouldn't inquire about IP addresses. And it might want to know about indexes on its databases, but not disk caching policies or i/o schedulers.

So the case of "too much magic" is not to become used to the discomfort of not understanding how underlying abstractions work, nor is it to stop using them. Instead you want to choose dependencies with well documented abstractions, implemented such that you can inspect if you need to.

This has nothing to do with Rails specifically. I see plenty of coworkers who dislike Django's large set of features, as too much bloat, or too much magic they don't understand. Instead of reading documentation, and becoming expert in Django's abstractions, they spin up Flask apps, slowly reinventing the wheels, but maybe not quite as round.

There's a huge difference between lack of understanding being irrelevant (hard drive) and lack of understanding being a blocker (framework).
So you're saying that you have to understand all of the magic rails does before it should be adopted for anything?
No. For example, on rails if I make a typo creating a table, I delete my whole project and start over from scratch. I have not been able to figure out how to edit the name of field. That's an impediment.
A very quick Google search for "rails rename column" will show that there's a method called "rename_column"

You can even search for "rails typo in migration" and the first result is a stack overflow post suggesting and explaining how to roll back the migration so you can edit it

Sort of, but I'd rephrase it. I don't need to understand how a harddrive or SSD actually works, I just need a consistent and well documented abstraction.
But Rails (and Ruby) do offer that. I agree that there's a lot of it, but it is there. Its abstractions are consistent, and Rails is generally well documented.
I don't know Rails enough to really comment on that, but if it takes 1 years to learn it all, I'd say it fails my definition of concise and comprehensible. It should fit in a single volume book that I can read in the evening over the course of a week.

That said, I don't have a problem with frameworks that are larger. Hell, I'm a .Net dev by day myself and that certainly doesn't meet that requirement. I just don't find it elegant.

You understand the underlying stuff after working with rails -- the complaint of Magic comes from (I believe) the fact that Rails is very easy to approach compared to other languages, so many people are able to create a lot with comparatively less understanding of the framework, so when things work or break, they are less likely to know the root cause/reason and blame it on 'magic'.
Everyone that feels this way please give Sinatra (http://www.sinatrarb.com/intro.html) a try

Complexity/perceived burdenwise, I think java is to rails as rails is to sinatra (or a similiarly small framework).

There may be a point where you want your functionality bundled and presented as a framework (if you, like me, think of frameworks as bundled and groomed libraries/functionality), but if you're not sure you're at that point (or don't know what that point is), try to start smaller (like sinatra) and go bigger, rather than the other way around (starting with the biggest tool you could find, trying to find the smallest).

I strive not to become a good developer on X platform (for Y language) but rather just a good developer who's proficient with Y language (and happens to be able to use X lib).

I used Sinatra on a project the year before last, and liked it so much I used it on two more the year after.

At this point I think I'd only choose rails where I knew from the start that it was going to be a big, complex, semi-monolithic service. I can definitely see value in rails for doing that kind of thing.. But for everything else Sinatra is a pretty good default position.

Seconded. I started on Rails and said to myself "this is doing too much on it's own, there is WAY too much going on".

Then I started using and looking into Sinatra, and it's way more basic and Ruby instead of Magic.

Sinatra is all well and good until you have an application complex enough that you want to start applying some interesting methods of code reuse across several routes, and want to share several facets of, say, object loading and authorization, or something around page rendering. At that point you run into the fact that all your tools for manipulating the request and response are built into a singleton object (your instance of Sinatra::Base) which begins to inhibit several very useful forms of code reuse (such as most OO inheritance schemes, for a start).

If I had a dollar for every minute I wasted rewriting things that I could have for nearly-free with (for example) Rails and CanCan and a quick call to load_and_authorize_resource in my ApplicationController, or a lame hacked substitute Rake task for ActiveMigration... that'd be a distressingly accurate description of way too much of my career.

Of course if you really are just building just the one microservice and don't have any complexity to manage, then yes, Sinatra all the way, 100%.

Would you mind giving a semi-specific example? It sounds like general use of ruby-based Dependency Injection is the solution to your problem, not necessarily rails.

Also, OO inheritance has some well-documented flaws as a code-reuse mechanism. I couldn't point exactly to what favoring composition over inheritance would look like in ruby (I've seen some approaches, but have an unhealthy dislike for the word "Factory" so they didn't seem great), but maybe there's a better way to share that code you're trying to share? Also, I don't think the solution is mixins because once you have too many of them how they interact becomes very hairy -- but regardless, I think a ruby solution exists to that problem as well. There is also the "has a" vs "is a" distinction but I digress.

Of course, there is a point where you shouldn't rebuild the wheel, but I don't think "shared functionality", or "access to persistent objects across requests" is that point.

To expand on what I mean, if these are the things you want to do:

- Respond to requests like an API server

- Serve static files

- Serve templated files

- Shared information across request

- Share application-relevant objects across requests (ex. marshallers, keystores)

- MORE STUFF

- ...

I think the point where rails makes sense is at/after "MORE STUFF", and possibly in "..." range.

Semi-specific example: I worked at a place that operated on a marketplace model. It was desirable for all marketplace submissions to be reviewed by a set of humans who were paid for that effort by the review. The existing system for doing this was in a bad state. By "bad state" I mean coded in a legacy system with no tests or even separation of concerns (think of a system that iterates over the results of a query, making additional queries and rendering JavaScript snippets to the page in this loop) and hitting scalability limits as it made exciting joins across the company's master database.

The company's architectural model dictated that the new version should take the form of a (non-micro) RESTful service layer handling the data in its own database (also facilitating the application of the problem to new lines of business for different varieties of marketplace item under different brands, improving visibility to the review process to sellers, and leaving open an avenue to outsource or automate certain parts of the problem.) We would migrate the legacy code over to use this layer instead, then we would write new frontend code with better workflows, then we'd coordinate with other teams to review new classes of marketplace item with different review criteria, then some year we'd have room for the pie-in-the-sky machine-learning-assisted review (assuming that something more important didn't come up instead.)

The result of this was a modestly complex set of abstract business-data objects, including marketplace items, all the metadata associated with them (editable by reviewers in this system), owners of items, reviewers, reviews, internally visible comments associated with the reviews, feedback associated with the reviews, review escalation, reviewer-supervisors, and a variety of other things which I forget right now.

I say RESTful: while not as pure as it could be, it actually did a half-decent job of being hypermedia-y and not merely "throw some JSON around over HTTP". Things had URLs instead of just IDs. Lists of objects were paginated with consistent ways to get links to the next/previous page.

Sinatra left us with uncomfortable choices between having excessive code duplication and the quality/maintainability issues it would bring, or writing our own logic to ferry Sinatra's various handles to the request / response / templating engine. We chose the latter, and I don't regret it - I regret that we sunk as much time as we did into that piece of code instead of building it on top of something with richer abstractions. It was the wrong choice for this particular application.

Thanks so much for the example, I understand why you made that choice!
If you like Sinatra and want to use something that's both fast and as simple as Sinatra you can try Kemal.(http://kemalcr.com/) A side note that it's written in Crystal(http://crystal-lang.org/) which is like Ruby.
I really don't like the way Sinatra has its own new DSL. I don't understand why Rubyists want a DSL for everything. I really prefer the class based approach of Camping.
I'm a Rails developer and I'm coming to feel this way as well. I agree with most of what's in the Rails Manifesto except the convention-over-configuration thing. Too much "magic" happening. Magic's cool, but I need to know why a certain thing works or why it doesn't work.

Rails is the only environment in which I've routinely found myself utterly stumped. Not because it's hard, because it isn't. But the thick layer of "magic" makes it awfully hard to debug sometimes.

We are in the same boat. I find myself relying way more on pure ruby classes.

It's also dangerous because Rails fits a specific niche, like he mentions--building (potentially) many web systems quickly. We chose to migrate large legacy systems to rails, which meant lots of the nice convention just. doesn't. work. Day 1, you're already digging in to figure out how to get around the magic!

For me, magic isn't the problem. I like the magic of conventions, but not when it is painful to pull back the curtain. For Rails, I think pain comes when magic stands on poor internals, which gets amplified when gems layer on their own painful workarounds...and I have to sort through all of it.

That's why I'm excited about Sean Griffin's work on the [Attributes API](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Attribut...). To do it, he's had to refactor out a lot of internal monkey patching so that (in Rails 5) there is only one source of type information for an AR model. This means gems can extend AR, I can extend AR, new requirements can walk into my door that I can deliver on without monkey patching or opening Pandora's box of a patch of crappy Rails internals. The magic is still there, but it's done it a way so the magic doesn't fall apart in unexpected, unmanageable ways. That's what matters to me.

As I've entered the more intermediate stage as a Rails developer, I've noticed that the "magicalness" that allowed me to do so much so quickly as a beginner, is often actually getting in my way. Perhaps when I reach the "advanced" stages, I'll learn to love the magic again, but I'm in this awkward stage of knowing what I want to do but not knowing how to tell Rails to do it (or stop doing it, as the case may be).
This was my reaction as well. "The menu is omakase" clearly violates "explicit is better than implicit".
Why throw out some catch phrase without providing specifics?

We all adopt certain amount of implicit choices in our lives because if we had to be explicit about everything, we'd be bogged down in choice-making and not really get anything done.

Why care about choosing the right ORM, Logger, templating engine when there's real business problems to solve? If there's a true business advantage to choosing an alternative, then substitute, but eventually you have to get on with the show and make something meaningful.

Strong defaults and conventions are a feature and Rails nails it better than any other framework out there.

Funny you should mention Django, because its the magic framework in python land that does all those things you label Rails doing and it does it way way worse than Rails.
That might plausibly have been true in the past, at least if you hadn't heard of Zope/Plone, but Django has been moving away from that for close to a decade:

https://www.djangoproject.com/weblog/2006/may/01/magicremova...

By now, it's hard to tell what you had in mind – something like contrib.admin or the global settings file?

Care to develop what he labeled and how Django is making does it worse than Rails? I have a hard time to see it knowing both Frameworks.
The only real magic left in Django is the metaclasses that ORM models use to turn class based fields into instance level descriptors.

  class Model(models.Model):  
      field = models.SomeField()  
rather than..

  class Model(models.Model):  
      def __init__(self, *args, **kwargs):  
          field = models.SomeField()  
          super(*args, **kwargs)  
Django is most certainly the framework that bundles a whole lot of choices together (similar to Rails), but the magic it performs on behalf of the user is extremely minimal.
I think your response shows why this is a great document. It's something any programmer can read and quickly determine whether or not Rails is right for them.
Yeah, I go back and forth. I find Ruby aesthetically pleasing, but, in a way, harder to understand. Python represents a lot of "truth" for me.

I would definitely say, though, that I find Ruby development to be amazing. And I find debugging in Python to be similarly awesome.

What do you use for Python debugging? I'm working on a large Python project coming from rails work and my biggest frustration is no equivalent to pry. I find Python debugging incredibly painful by comparison.
In terms of web debugging instead of general code debugging tools mentioned by others, the common Python frameworks have toolbars based on Armin's Werkzeug one:

http://werkzeug.pocoo.org/docs/dev/debug/

eg...

For Pyramid: http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/...

For Flask: http://flask-debugtoolbar.readthedocs.org/en/latest/

For Django: https://github.com/django-debug-toolbar/django-debug-toolbar

I have no idea how well they compare to equivalent Rails tools though.

PyCharm's debug tools are top notch. It's based on PyDev, which is an alternative that can be used independently in other IDE, but it's really the integration with the IDE that makes it a much better alternative than using, say, PyDev in Eclipse or vim.

Here's a brief overview of PyCharm debugging functionality:

http://pedrokroger.net/python-debugger/

I really like the ease of use for remote debugging. It's essential in helping to maintain a Linux development environment that closely resembles production while actually coding on a work laptop that runs Windows. Beyond that, I often use the remote functionality to run one-off scripts to retrieve and process data from a production Django system (though, generally this is restricted to 'read' operations unless it's been tested in dev).

Not a ruby dev so don't know how they compare to pry but I've been a happy heavy user of ipdb [1] and pdbpp [2].

[1] https://pypi.python.org/pypi/ipdb [2] https://pypi.python.org/pypi/pdbpp

pdb and pudb seem to be reasonably popular. I use pdb myself. I haven't used pry, so I'm not sure about equivalences.
Words written in a language you don't understand are always hard to read.
Couldn't help but think the same thing as I read this.
Similarly, Python turned me off immediately when I tried to exit the REPL with "exit" and it goes "The way you exit is with control-D." It fucking knew I wanted to exit, and instead lectured me. WTF, Python? Seeya, dick.
That's because there's no magic in the REPL. You're typing nothing but regular Python code.

'exit' is an object. Expressions typed at the REPL are stringified. Thus, when you type 'exit', it gets stringified, and thus you get that message.

'exit' is also a callable, so if you type 'exit()', the REPL will exit.

The same goes for 'help', 'copyright', 'credits', and 'license': they're just objects that get stringified.

Having the REPL work the way you want would mean building magic into it, and magic avoidance is part of the language's culture, so that's not going to happen. Having it print that message is a compromise between keeping things friendly and building magic into the REPL, if you consider printing the result of expressions automatically to be magic.

The Python REPL isn't being a dick, it's being parsimonious and predictable in its behaviour.

Yet the code behind the REPL knows exactly what I was attempting to do and tells me that.

This is what Ruby developers mean when they say they optimize for developer happiness over bowing to the will of the computer.

/uses Python regularly and enjoys it.

Those assumptions that "optimize for developer happiness" 95% of the time make the developer's life hell the other 5% of the time. It happens often enough that "explicit is better than implicit", within reasonable constraints like dynamic typing, is one of Python's core values, and experienced developers usually come to appreciate it.

It's best to design an API that makes what's happening clear without being overly verbose or esoteric, and without making assumptions, especially not dangerous assumptions that may cause you to lose data (exit may close the REPL and cause you to lose your session when you forget that exit is a special word in REPL mode). My personal feelings are that Python has done a better job at this than any other major language. I have major projects in both Ruby and Python and I enjoy the Python ones 100x more because of this.

This document partially explains why; DHH is normalizing shortcuts because it helps him gain new users, even though working around those same shortcuts becomes a major PITA when you need to go a little bit off the beaten path, which happens in one way or another in most real software. There is a way to make these things explicit with only marginally more typing/"effort" (for example, requiring parens to call functions with no arguments, making it explicit and obvious when someone is referencing a variable vs. when they're calling a function), thus avoiding the complexity down the road.

> and experienced developers usually come to appreciate it.

I find statements like this a bit condescending. It's like when Java developers say 'Oh, that's nice, but I work on big applications." "Obviously you don't get it because you're not experienced. Experienced developers get it."

I'm sure that wasn't your intent. But it still irks me a bit.

I've been writing software for a very long time, in many languages and paradigms over the years. I believe that qualifies me as an experienced developer. My years of experience tell me that if I only experience pain 5% of the time, then the 95% of the time I don't experience the tools I use making me do extra work more than makes up for it. :)

>I'm sure that wasn't your intent. But it still irks me a bit.

Correct. I meant it inclusively, like most of the Python community is either experienced developers who've worked in a lot of languages and have sought refuge in the sanity of Python or the apprentices of such developers. Python doesn't have a figure like DHH to give it sex appeal, so not many new people use it.

There are definitely experienced developers who have not yet had occasion to seriously enjoy and appreciate Python.

>My years of experience tell me that if I only experience pain 5% of the time, then the 95% of the time I don't experience the tools I use making me do extra work more than makes up for it. :)

I meant this as a count of the number of issues, not the amount of time it takes to resolve them. That 5% of problems caused by non-obvious implicit magical behavior usually take an inordinate amount of time to debug and solve, and then the workarounds are usually disgustingly ugly because the framework had never conceived that someone might have a valid reason to circumvent their magic. Even worse, this locked-down, "looking inside will void your warranty" attitude (which Rails often calls "convention over configuration") frequently means that the workaround must be somewhat pervasive and ugly up your code not just in one place, but in several places to really resolve the problem.

Experienced developers may not encounter this often if they don't use a lot of magical APIs that promote the systemic ambiguity of "do what I mean".

Ruby + Rails has always been better at social engineering than any other language or environment.
> Yet the code behind the REPL knows exactly what I was attempting to do and tells me that.

Not really – all the code knows is to stringify objects, and it saw the object called `exit`, for which the stringified version is that error message. But nowhere in that flow does the code know how call anything that could exit the REPL.

/doesn't use Python regularly and thinks the smart move would be to make the REPL understand certain things as commands instead of normal Python code.

Well, the __repr__ method on the 'exit' object is getting called. So that method could actually call sys.exit() and cause repl to exit. But it would be very rude and strange behaviour for calling a __repr__ method to exit the interpreter, or, indeed, to do anything other than return a string representation of the object.
What someone more clever than me ought to do is write up a huge investigation of an obscure bug caused by exit.__repr__() calling sys.exit(), and then only at the end comment that it's not real, but could've been if Python went down this road.
Why? quit and exit are imported exclusively at the REPL: https://docs.python.org/3/library/constants.html?highlight=e.... Would it really be any worse than automatically assigning the result of the last expression to _?
Point well made.
And quit calling exit isn't weird?
If the REPL initiates an exit via throwing an exception (eg.g new QuitException ) and having the top level of the stack catch it then handle the termination, then you could have the stringification of exit throw that exception too.

It's not really magic at that point. It's not really 'magic' at that point to me.

Granted some people might say its surprising for stringifying an object to call sys.exit() or throw an error that'll cause exiting.... But within the context of the REPL its not surprising.

Whatever method you take, whether it's a special case after reading a line, or some other weird hack, you're adding magic.

With Ruby, this isn't a big deal because if `exit` is a function, then typing `exit` will call it, and you need to use the unary `&` to bypass this and have the callable treated the same way that Python does (which is roughly equivalent to a Ruby Proc).

Ruby doesn't require the hack, but Python would, for better or worse.

I wrote nearly the exact same explanation minutes after yours, but I've deleted mine because yours is clearer.
I understand the technical reasons behind it.

First/early impressions do matter, however.

But also, how is bundling that explanation into the stringified version of the "exit" object not as magicky as Ruby putting an "exit" method on Kernel which is in the root namespace of the REPL and exits the current process (with an optional exit code)?

Slight correction, `exit` in IRB is actually not the same as `Kernel.exit` (as IRB actually allow IRB sessions to be nested, using `Kernel.exit` would quit the whole program rather than just the current session). It is actually an alias of `IRB::ExtendCommandBundle#irb_exit` that throws `IRB_EXIT` to stop the eval loop of the current session and allow some pre-exit hooks to be assigned to it.
"Slight correction", good one haha

That said, technically correct is best correct, so here is your upvote!

Ruby's "exit" doesn't have to be as magicky owing to how simply typing "exit" in Ruby is a valid way to call a function with no arguments. The problem, if you can even call it that, is that Python doesn't have the separation between functions/methods on the one hand and procs on the other.

That is a critical difference that needs to be kept in mind.

Edit: grammar fixes. I didn't reread by comment before posting. :-/

strange, mine doesn't do that:

    Python 2.7.10 (default, Sep  8 2015, 17:20:17) 
    [GCC 5.1.1 20150618 (Red Hat 5.1.1-4)] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> exit = "I'd like to assign a value to this non-reserved word!"
    >>> exit
    "I'd like to assign a value to this non-reserved word!"
Ha ha, got a laugh out that, though I'm a Pythonista. Obstructive software is irritating, of course.

Couple of points though:

1. It also says you can use Ctrl-Z (though you still have to hit Enter after that, which is a bit of a pain).

2. You can just use Ctrl-D. Works on both Windows and Linux, no need for Enter. Smooth and quick.

And it works in like every repl/ interactive shell program ever. Why bother to memorize all these other ones?
control-D doesn't work in iex (Elixir's REPL) or erl (Erlang's REPL). Control-C-C does.
Fair point. I mostly just mash ctrl-c and ctrl-d until the thing quits, works for pretty much anything except Vim.
That's a lame reason to not use Python
Here's better ones:

1) Did they fix the thing where you had to use __self__ practically everywhere? Double underscored variable names also look like crap, especially when they're everywhere. It looks like a really shitty design flaw, having to use __self__ this and __self__ that everywhere. (This situation may have been ameliorated since I last looked at the language.)

2) Not everyone likes significant whitespace. But moreover...

3) If you code for long enough, you'll eventually realize that Ruby, Python, C++, etc. etc. are all fundamentally flawed due to OO making it too easy to write spaghetti-dependency code with mutable state everywhere, which means EVERY OO CODEBASE NO MATTER WHAT eventually becomes an unmanageable bugridden long-test-runtime complexity tarpit.

They should stop calling it "technical debt" and start calling it "object-oriented debt" ;)

So you eventually convert to the Functional Programming™ religion. ;) And that is the real reason not to use Python (or Ruby or Java or C), IMHO. But don't take my word for it, here's John Carmack basically saying that everyone should use functional paradigms (even in OO languages) if at all humanly possible: http://gamasutra.com/view/news/169296/Indepth_Functional_pro...

1) __self__? I don't think I have ever seen that. Are you combining two problems? All metamethods are quoted with dunderscores, and the first parameter of each method is "self", both of which annoy some people...

2) That is a reasonable choice. I like expressing myself once, instead of repeating myself in both whitespace and brackets or whatever, but I get the argument for autoformatting after copy/paste. Of course, lua does this the best, by having neither significant whitespace nor semicolons, and just figuring out where statements end from the grammar.

3) Objects are wholly optional, and can mostly be avoided. the functools and itertools libraries go a long way to bring the functional religion to python.

You should probably be careful there -- shared mutable state is the problem, and as Rust demonstrates, attacking the "shared" part can be just as effective (if not more so) than attacking the "mutable" part.

Both come with tradeoffs, but Rust's borrow checker seems, anecdotally, to be easier for people to understand than the category-theory word salad that litters most strictly-immutable FP language discussions.

I have seen showstopper, up-all-night bugs with BOTH shared mutable state (see: the reason it is nearly impossible to reduce a certain Rails app's test suite runtime below about a half hour without significant rewriting) AND single threaded mutability bugs (in one case resulting in me taking a month to fix a login bug that nobody else at the company was able to find out in over a year... It was a hash mutation deep inside a middleware affecting auth cookies)

A simple example of a single threaded mutability bug is passing an object by reference to a method which then (either intentionally or unintentionally) mutates the object, to the complete ignorance of the caller up in the call stack. The only thing stopping that is literally, programmer skill. No language constructs. At least in all the OO langs I ever worked with.

You really should try out Rust. The scenario you describe would not compile in Rust.
It's self, not __self__ (it has never been __self__). Double underscore names are used for special methods, which are typed relatively rarely, the most common probably being __init__.
Most Python code does not involve the creation of classes, so I think you're wrong to associate it with true OO message-passing languages such as Ruby and Java.
I really like IPython for that reason: it does what I expect. There's a bunch of magic, but they explicitly call it out as such. It's been built to handle that frustration.
Shame that you write off an entire language from this trivial experience.
I'm a Ruby guy that has tried Python several times. My takeaway is always that Python is beautiful and wonderful, but it just has all of these tiny little annoyances that build up and make me rage quit. Exiting out of the REPL is one of these things. I guess I spend so much time working with, and get so much work done in, IRB that anything that trips me up in a REPL just gets under my skin.

I really, really want to like Python, but I just can't seem to stay with it long enough to work through all of the "weird" stuff. :/

I became a Ruby guy. It's like Python, but with less __self__'s, and insignificant whitespace. ;)
Starting to mess with python for first time (did hello world yesterday).

_____this is so offputting______

But otherwise language seems cool

I think they did something to fix it, but back when I was investigating Python, it was useful to pass references to yourself to everything for some reason, so every method passed like __self__ to itself, it seemed like __self__'s were everywhere. Coupled with how fucking ugly (opinion, of course) double-underscored variable names are, it just did not give me a great impression.
You're wrong and you confuse two different things.

So called "magic" methods, which are called by Python in various circumstances, are surrounded by double underscores. One of such methods is __init__, which works as an initializer for newly created objects.

The other thing is that all methods, unless otherwise specified, take a pointer to the current object as a first argument. So when defining an initializer that takes no arguments from the user you write:

    def __init__(self): # etc.
I think only __init__ and __name__ are widely used in Python, all the other such methods are very special purpose and very rarely used outside of deep library code.

BTW: Being "ugly" or not is not the best characteristic you can base your opinion of a language on.

Isn't it great that people can find the tool that suits them best? Now if only we could make it so people were secure enough in their choices and preferences that they didn't have to point out that they prefer X in a thread specifically focused on Y, we might avoid a bunch of needless gnashing of teeth and flame wars.
Seems like a reasonable critique in a thread exalting the amazing success of Y. I'm sure the developers of Y care about a newbie perspective of Y, and a comparison often serves as a good tool to show why something is confusing.
How was it a critique? He just said he didn't like it, and exaggerated a few things.
Maybe its because I'm a JavaScript dev, but I seem to clearly understand the critique (vs maybe if you are a Ruby dev it just sounds like "I don't like it"): too many built-in methods, lots of magic behavior, and an over-reliance on syntactic sugar. Seems pretty straightforward.

Possible non-code-altering solutions to the problems include: 1) limiting educational material on Rails to only use a subset of the built-in methods so that newcomers aren't overwhelmed by feeling they need to learn the entire std library before being able to make progress, 2) Using more explicit versions of code vs the more terse but harder to understand versions enabled by syncretic sugar to ensure that people coming from other languages first understand what is going on, then are blown away when they discover you can write it in an even cooler way, 3) a deeper explanation of what is going on under the hood to make the magic seem "OK".

I considered it an "I don't like it" post because aspects that the top level poster described are intentional features, not problems or issues that need to be fixed (at least according to DHH & co).

And I guess the word 'critique' connotes a somewhat more detailed, analytical assessment - at least to me.

I guess the perspective difference comes from having run and worked in open source myself in the past: its a better strategy to try to understand the pain point the user is facing regardless of how "analytical" the assessment is. A lost customer is a lost customer, and in this particular case, this seems to be a common critique. Given that the critique attacks as you say intentional features, that's why my mind went directly to the problem of education of the features. Perhaps this class of use is a lost cause: or perhaps this reflects a gap in the learning curve that could be strengthened.
Python and Ruby come at problems from different directions, and you have to go with the one that suits you. Python-philosophy says "explicit is better than implicit" and Ruby-philosophy says "convention over configuration."--at some level these are irreconcilable. If you want to work one way, the other way is not going to make you happy.
Well, we might as well get rid of the comments altogether if making a statement related to the content of an article is grounds to consider it "needless gnashing of teeth and flame wars".
It's perfectly fair to compare competing products in an apples-to-apples comparison.
I find people mostly tend to get defensive when they are being attacked. I think as long as people can police their own tone effectively, that it prevents discussions from devolving into something toxic.