Rails is not written in Ruby
solnic.codes
solnic.codes
But as a counter-example to that, I'm constantly amazed that there are a ton of Ruby gems out there which pretty much work as advertised outside of Rails (even if Rails gets a privileged happy path during config). Not to mention you can use ecosystem stalwarts like Puma, Rack, Sidekiq, and many other subsystems outside of a Rails app entirely. Heck, you can pull just bits of Rails in if you really need them. Want Active Record for your ORM but nothing else? That's totally possible! (But of course then you get a bunch of Active Support stuff too which is what the author objects to.)
Personally I love Active Support and actively (heh) bring it into my Ruby projects if it's not there already. To a certain degree, everything people say is a "bug" about Ruby's metaprogramming/monkeypatching is a feature in my book. The fact that "Ruby core" is simply a substrate upon which you can build your own flavor of a Ruby-plus language—be that "The Rails Way" or something else entirely—is amazing. In that respect, I entirely disagree that Rails is not written in Ruby. Rails shows us how you _can_ (and probably should!) use Ruby to build DSLs which suit your specific application/framework purposes very well. It's not a bug. It's a feature. And it's why most other tools which claim "Rails but for Language X" fall short…they can try to replicate features of Rails, sure—but because it's not Ruby, it misses the whole point of using Rails, which is that you get Ruby "for free" to sweeten the deal! That's the (not so) secret sauce here.
I must come to Ruby from a very different angle to you - but I don't even really get what you mean. Why would the majority of gems have anything at all to do with Rails or need it to work as advertised?
For example the top-ten gems have got absolutely nothing at all to do with Rails have they?
Why would the majority of gems have anything at all to
do with Rails or need it to work as advertised?
If you put Rails-specific functionality in your gem, now you have a dependency on Rails, and given the seamless and transparent ways in which Rails (specifically, ActiveRecord and ActiveSupport) extend Ruby, it's easy to unintentionally wind up making the non- Rails specific bits of your gem depend on Rails somehow.Additionally one could say that AS/AR serve as sort of a defacto standard library of sorts at this point. You copy some code from Stack Overflow, it's got a dependency on one or the other, etc.
I'm not endorsing that state of affairs.
I actually don't know how I even feel about that state of affairs. The mostly-transparent extensibility of Ruby is the best and the worst thing about it. It's great if you're writing a framework or gem; but it makes it really tough for beginners to know what is doing what.
Do a lot of gems do that? It's never something I've encountered when looking for gems to use in my Sinatra, command-line, etc applications.
Be careful about to which Ruby Community you believe you are speaking for. Keep in mind that a lot of development of the language itself is happening in Japan, in Japanese, where most people won't be regularly reading and posting on Stack Overflow. 5 of the core contributors live within 15 minutes of each other and meet and discuss issues in person.
If you've watched recent developments in the Ruby Community there, especially in the areas where the Ruby team has made large performance gains to improve the language, Embedded Ruby (as in Ruby running on IoT devices, or mruby, not ERB templating) is actually a very hot topic and isn't even in the orbit of Ruby on Rails.
Japan is still seeing growth in adoption of the language and has a huge number of Ruby conferences. There's multiple "regional" versions of RubyKaigi in Japan throughout the year.
That's what your often hear from "Rails skeptics". According to them people don't even know what is Rails and what is Ruby, and that because of this, gems either have Active Support as explicit dependency, or use Active Support extensions without knowing it and then don't work outside of Rails.
I don't think it's a coincidence that we got _why, DHH, pragmatic programming and the like as soon as Rails put Ruby on the map back in the mid 2000s.
It has a lot of Smalltalk in it in a way that is easy to step into from another language.
It's odd though, how unreadable Python code can end up being if you don't bother caring about how you write code.
some_array.has_my_thing() is better than has_my_thing(some_array). ?
The former, the extension has so many issues, it messes up namespacing, it's unclear where it came from, etc. etc. The latter is just a function. It's not longer, or complex, it's just more straightforward. I really think things where you extend or override built in types is almost always just a bad idea, because the alternative is almost always just as concise and doesn't confuse everyone.
some_array.do_thing().do_another_thing().do_something_else()
versus
do_something_else(do_another_thing(do_thing(some_array)))
Note the first example could be an example of chaining - where each function returns "self" and we progressively transform the object. Or it could be simply each function returning different values which are operated on. Either way, the pattern is usually cleaner and easier to debug than the nested equivalent.
some_array |> do_thing() |> do_another_thing() |> do_something_else()
Even though Elixir is functional and aggressively not object oriented, if you squint at this pattern you get the readability of an OO-style method chaining API.
See also:
https://mamememo.blogspot.com/2019/06/a-brief-history-of-pip...
``` 1.. |> take 10 |> map {|e| e2} |> (x) ```
which is equivalent to
``` (1...).take(10).map{|e| e2} ```
So in order to use it you still need extensions on every single object. The only reason that looks nice is because in this example you continuously call methods on lists that return a list. You can't add `my_custom_list_operation(list_input)` in between.
Going forward, the pipe operator is going to be showing up in many new languages. I've used elixir a lot, and the pipe operator is a genius piece of syntactic sugar.
some_array
|> do_thing()
|> do_another_thing()
|> do_something_else()"string".do_stuff() - do_stuff() is a method on the "string" class, with all of the potential gotchas that might come with an OO implementation. Does the method mutate the internal state of the object? Does the particular class override the default implementation? Is it a monkey-patched implementation (the issue raised by this article)?
"string" |> do_stuff() - do_stuff() is a standalone pure function. The data is immutable and does exactly what it says.
a.this().that() requires that a.this() returns an object on which .that() can be invoked.
(that (this a)) requires that (this a) returns an object that is a suitable argument to that.
Ruby syntax apparently is especially nice if you know Japanese: https://flatironschool.com/blog/ruby-and-the-japanese-langua...
(do_thing . do_another_thing . do_something_else) some_array
Are we talking about Ruby? Because it does.
It has both:
(do_thing >> do_another_thing >> do_the_last_thing)[arg0,…,argn]
And: (do_the_last_thing << do_another_thing << do_thing)[arg0,…,argn]
(Where the things in the composition are procs/lambdas, the closest thing Ruby has to functions as distinct from methods.)I wasn't thinking specifically of Ruby (and indeed I don't know Ruby, nor was I aware that it has this kind of support).
Rather, I was thinking about programming languages in general, since the top-level comment seemed to be talking about the general principle.
Out of 5 languages I use on a regular basis, only one of them has _some_ support for function composition. But even then, not as flexible as what you're demonstrating here. So my point is that across programming languages, the chaining approach is generally more readable than the composition approach. And that in turn favours methods over global functions.
If I ever start using Ruby, I would probably be on the fence about which approach is better though.
Which, believe it or not, they still teach function composition as good old g(f(x)) in high school algebra...
(do_something_else . do_another_thing . do_thing) some_arrayThat Ruby also implements X.foo().bar().baz() is a weird thing about Ruby that can be very powerful, but is unrelated to the dot syntax.
I feel like roughly half of Ruby programmers treat it as "Lisp but better", which leads to confusion about the OO aspects of the language.
Admittedly I don't think anyone really prefers the second way of writing it, but that's not the same as being ecstatic over the first one.
For example, in C, the array operator is mostly synthetic sugar for pointer addition, and could be removed. Should it be removed? Of course not, cause programmers are not thinking of pointer arithmetic when indexing an array.
And as much as I love lisp, it's one of the top reasons none of them ever had widespread adaption. Nobody likes having to do math like this (* (+ (sqrt 4) 2) 4).
And because of easy macros (another advantage of the syntax), you can easily add syntactic sugar as you see fit where you care.
Some languages (like C# and Rust) support extension methods, so you can use method-style to invoke separately-defined procedures. But in other languages, method-style APIs become an inextensible privileged zone, and I personally prefer to minimize the number of inherent methods as much as possible. By preferring direct-style invocations, I avoid the temptation to stick a useful method on the class itself "for convenience".
Or for quick and dirty debugging, with print statements, being able to put a println in the middle of any method call, just by adding a .also{println(it)} to the chain.
Isn't the D solution actually that:
A.B(...)
is syntactic sugar for: B(A,...)
if A.B() is not specifically defined.(Yours both flips the elements and reverse the direction of sugaring, as I understand it.)
In the second example, we have only function application. Put a breakpoint or print statement into do_another_thing and it is sure to go off.
(do_another_thing could support OOP dispatch as a generic function. A generic function is still something that is called first then dispatches methods based on its arguments. E.g. in Common Lisp we can (trace do-another-thing) to trace the calls.)
I just don’t understand why Python thinks it necessary to define global functions for something that naturally should exist as a method.
And, of course, there’s no way to know up front when to use a global function vs when to use a method.
len(my_list)
is just sugar for my_list.__len__()
if you feel strongly about the convention: https://docs.python.org/3/reference/datamodel.html#object.__...And for other unary "magic methods", the disconnect can be even bigger. E.g. iter(x) is not just a synonym for x.__iter__() - it can also produce an iterator for something that only provides __len__ and __getitem__.
In the Ruby way, with otherwise similar setup, you would have a mixin that provided __iter__() given __len__() and __getitem__(), and classes that had the last two but wanted the behavior of the first would just include that mixin class as a parent, rather than having a separate branch in iter() to handle the different cases. (Ironically, its one way that Ruby is less TMTOWTDI than Python is.)
Python does have such a mixing, it's collections.abc.Sequence https://docs.python.org/3/library/collections.abc.html#colle...
somestring=somearray.join(' ')
Versus Python's: somestring=' '.join(somearray)I don't use either Ruby or Python but I think that the first way is more natural.
foldLeft(tail(array), str(head(elem)), (acc: String, elem: T) -> acc + ',' + str(elem))
Which makes the array method easier to understand than the string method. Funny how I thought it was _obvious_, but you find the opposite obvious. 1 ",".join(["a", "b"])
2 ["a", "b"].join(",")
3 ",".split("a,b")
4 "a,b".split(",")
These method names feel like active form so 1 and 3 should be correct but it's 1 and 4 despite "a,b" obviously is not splitting "," but it's splitted by itIn Ruby it's 2 and 4.
I'm out of luck twice but at least Ruby has a and b always to the left and the comma always to the right so I always remember it.
print('Hello '.join('world!'))Very often, when I'm learning new code, there's one line I'll write over an over in console bindings:
> some_object.methods
It provides a super-easy way to learn what methods are actually relevant to `some_object` without having to pour over every method available in the global namespace and figure out whether `some_object` is a valid argument to each. You can also apply set logic like `some_object.methods - {}.methods` to return only the methods available on `some_object` that AREN'T on typical objects, which is a very, very, very powerful learning/exploration tool.For example,
irb(main):003:0> PaypalService.methods - Service.methods
=> [:create_prepay_invoice, :build_client, :capture_invoice_funds, :get_order_info, :get_plan_price]
Side note: you can also call `.source_location` on any method to see exactly what file/line it's defined in (even in core ruby/rails methods). There's a lot of other inspection methods available to learn more about objects and what you can do with them, which IMO is a benefit over your latter example.It's this great ease of introspection that makes Ruby so fun.
It's a pretty trivial matter, so I wouldn't go breaking encapsulation everywhere just to support it. D has a feature called Universal Function Call Syntax where a.f(b, c) is sugar for f(a, b, c) for any free function, rendering those issues moot. I just wish more languages would pick it up!
I really don't see what chaining via methods bring that simple function composition doesn't.
Visual flow from input to output.
(Ruby can compose either way, so you can build a pipeline of procs in flow order, but if you aren’t chaining the input still goes at the end, not the beginning.)
My ideal preference is for a language to support universal function call syntax where x.f(y) is equivalent to f(x,y) always and also include support for custom operators (or at least include common functional operators).
Ruby has composition operators in both directions:
given:
x = 2
f = lambda {|a| a+1}
g = lambda {|a| a*2}
the following are all equivalent: f[g[x]]
(f>>g)[x]
(g<<f)[x]
What’s inconvenient is that to use it with methods, even ones that can be invoked in ways that look like function calls in other languages, you have to realize a method object, since method names don’t name objects. Or, as Matz says (I don’t think of Ruby as a Lisp at all, but in this context it makes sense) Ruby is a Lisp-2, not a Lisp-1. 2.g.f
g(2).f
(g >> f)(2)
(f << g)(2)
with a strong preference for the first one. That's the Elixir pipeline with a dot instead of a |>The second one is OK-ish but it turns weird with a long list of functions.
The last ones are not nice to read, too much syntactical overhead.
There is a ton of new syntax added to Ruby in the last years and they had to find a way to fit it into the space between what was already there. The results are not always nice IMHO.
They’ve been among the methods for invoking procs since at least Ruby 1.8 (2003); there are several – all of these are equivalent.
f[x]
f.(x)
f.call(x)
f.yield(x)
> I’d like to write one of: 2.g.f
g(2).f
(g >> f)(2)
(f << g)(2)
Ruby is a pure OOP language, and “.” is the syntax for method invocation. So its really not available as a composition operator. So the first two don’t work for that reason. Further extending Ruby’s syntax to make unmodified parens both invoke methods and serve as syntax sugar for .call() on objects would probably be problematic (that’s why “.()” was adopted.)> There is a ton of new syntax added to Ruby in the last years
None of the stuff related to this except the composition operators is added recently. And those aren’t really syntax, just normal operator methods added to the Proc class.
The latter is in the global namespace (or you have to create a module/namespace for it). The former is encapsulated in a class. Encapsulation is nice.
I think a lot of these debates come down to repl development versus ide development. I’ve done a lot of both, but many complaints come down to people on one side not understanding the way the other side works.
No, it doesn't.
Pervasive OO style and monkey-patching core classes are orthogonal. Ruby favors the former strongly, and supports the latter, and Rails rather heavily leverages the latter (to the extent that it has been harshly criticized for it by much of the non-Rails Ruby community), but there is no necessary relationship between them.
How do code completion work in languages where the predicate goes before the subject?
When declaring a variable, in a typed language you could use the variable type to filter down the possible function completions based on return type; in practice, I don't think any LSPs do that.
And, sure, I guess you could `Array.has_my_thing(some_array)`... but that's just re-arranging the mixin structure behind `Array`. It only looks different until you start peaking under the hood.
IMHO, sometimes you want a function because in your problem domain there's a universal (or universal enough) act/question/etc, and sometimes you want a member function because in your problem domain the act/question/etc only makes sense given some particular context.
That depends on the implementation. A monkey-patch is definitely problematic, but when it's properly scoped, like C# extensions, Rust Traits or Ruby Refinements, it's quite elegant.
Because of that, you can not have 2 parallel versions on the same lib, in the sane app, not possible. You load classes once, and they are accessible everywhere, magic! No need for loader headers as in JS, Python, Java, Go, ...
That said, ruby devs like to have "sane" extensions like to_json. People proffer to write "@object.to_h.to_json" instead of "JSON.parse(@object.to_h)" and not to think about loading json lib. I agree that Rails is abusing this too much.
with the very latest ruby version, you actually can:
https://rubyreferences.github.io/rubychanges/3.1.html#kernel...
Kernel#load: module as a second argument
(the link jumps to some random place for me)Well, you always could, it was just a bit more involved:
So, I think it's fair to say that the advantages you are lay out are more tangible than the other side of the trade-off.
Having written too much Ruby code I'd still cringe every time I have to pass the array.
Edit: To make it actually clean it should be 'MyArrayLib.has_my_things(array)'. Still really dislike it despite all the logical arguments for it.
my_thing.in?(some_collection)
since it affords the object agency to decide for itself.For some
method(thing a, thing b) thing {
return a * b
}
is not better than method
a * b
end
If you're an "artist" type that care about the aesthetics of your code you tend to prefer the second.one of the reasons i prefer python to ruby. sorted(my_thing) will sort anything that can be sorted. no need for monkey patching -- you just call it and, if it can be sorted, it will be sorted.
Python does the exact same thing, it just hides it from you using ugly __methods__ that the language itself will call by convention. What determines sortability? "Anything that can be sorted" just means anything that implements the __lt__ method. In Ruby, this method is called <=> and the Comparable module builds upon it.
You can add iterators with __iter__, reversed() calls __reversed__, etc.
The difference is that most likely that CPython and CRuby started with different capabilities to map native implementations into the language.
The biggest difference between the two is python adds more functions into the global namespace, while Ruby pushes pretty hard to encapsulate logic within types.
To be sortable, you collection has to be a collection, and it has to have comparable elements. That has to be provided by the type itself.
The difference between Python and Ruby is how to add more functions. If you make a library to sort collections (or format phone numbers), the Ruby way is to monkey-patch collections (or numbers). Whereas the Python way is to offer a free function that takes an iterable (or number). This is what we are discussing.
Of course Python classes have methods.
Iterables use functions like __len__ and __next__. The with keyword is just a thing that implicitly calls __enter__ and __exit__ on a context management object.
The discussion has mostly been about instance methods defined in classes, and how do you define “like sort” in this claim?
list.sort() is a method in Python; it's the equivalent of Array#sort! in Ruby.
To that I reply that no, there is no method to sort hidden in __methods__. This is not how functionality is added to classes in Python. If you want to add something that works on all iterables, you don't insert new methods in iterable classes, like you do in Ruby. That free-standing sorted() function really is a free-standing function, it is not actually a method "hidden in ugly __methods__".
You don't in Ruby, either.
If you want something that works on all Enumerables, you normally write an (instance or class/module) method on some other class or module that takes an argument that supports the methods provided by the Enumerable API.
(In Python, you might do this or write a bare function, but Ruby doesn't have bare functions; it's possible to make proc objects in a library which is the nearest equivalent, but you’d almost never do that, because module methods are much more convenient.)
In either Python or Ruby you might also monkey-patch existing classes to add functionality to the., and this is slightly more common in Ruby than Python, but because Python iterable, unlike Ruby Enumerables, don't have a common superclass that isn't shared with non-iterables, you couldn't easily do a “for all iterables” thing in Python [0] by monkey-patching because you don't have a good target to monkey-patch, whereas in Ruby you might do something for (nearly, because duck typing is a thing and it's possible someone could build an enumerable by implementing the whole API without mixing in the module) all enumerables, not by adding methods directly to individual enumerable classes, but by monkey-patching the Enumerable module itself.
The main difference here is that Python (despite having support for mixins because it uses multiple inheritance) tends in it's core to use a mix of OO public APIs and procedural public APIs built on top of OO protocols using (often dunder) methods, rather than either consistently-OO or consistently procedural APIs, whereas Ruby is consistently OO and uses mixins to provide shared functionality building on a narrower protocol where Python would use a suite of functions. (Python tends to be more OO in its stdlib and third party libs than in its core, except specifically when supporting protocols defined in the core and consumed through built-in functions.)
[0] except by monkey-patching object with code that tries to use the dunder methods that underlie the iteration protocol and fails if they don't exist, but while theoretically possible I don't think anyone would do that.
Your point about Ruby syntax not really being meant for free functions is well-taken though.
sort() in ruby is declared on Enumerable. Sorted() in python is declared to take an iterable first parameter. They are pretty much equivalent, except python has built-in functionality to reverse the order of the output array, while ruby you would have to negate your sort function.
Ruby however allows types to override sort, such as when the underlying type is a red black tree which is already sorted. For duck typing, you can also declare a compatible sort method on types which do not implement Enumerable - doing so in python would require a separate method.
array.sort # returns a new collection
array.sort! # sorts in place
(Ruby convention is to add a ! when methods directly modify the object they're called on.)No, Ruby convention is to add a “!” when there is a “less safe” version of an operation, for which a “more safe” version exists without a “!”.
Mutating versions of methods where a nonmutating version also exists are one common example, but not the only one, and mutating methods without a nonmutating counterpart don't get “!”.
Awhile back I gutted the code and ripped out 60% of it, including all the monkeypatching and the maintanence burden on every ruby release dropped sharply.
(And I'm assuming we're talking about the more polite kind of Monkeypatching where you're adding methods to global classes that are net-new, not the practice of opening up a class and scribbling over methods that you didn't write and replacing behavior, where should you should always mental picture your SemVer contract being lit on fire)
IME you don't usually do monkey-patching, although there are circumstances where it makes A LOT of sense (usually test suites). It's one of those sharp knives - safe if you plan out using it, dangerous if you don't.
I believe Ruby's open classes were also a reaction to the locked down libraries that were common to Java. They were impossible to extend and resulted in a rats nest of adapters, visitors, and every other pattern in the book. In Ruby the programmer is in charge, not the library authors. The ability to do 1.day.ago was not just cute, but revolutionary, and huge productivity boost for anyone coming from C, or Java, or C# - the predominate languages of the time.
I can understand why it might not be to someone's taste, but I find the idioms to be generally delightful to use and, in most cases, extraordinarily convenient.
But, as always in the Ruby ecosystem, if you don't like it, find the thing that brings you joy!
One of my most consistent experiences with Ruby is doing something the "wrong way" - but being able to do, due the language flexibility - and then later coming back to the problem and realizing the better solution, which ends up being more satisfactory and happier in every way.
Ruby lets you do whatever you want, however you want, but it makes the right way the most satisfying. Very handy for iterative development.
https://docs.ruby-lang.org/en/3.0/doc/syntax/refinements_rdo...
Those are similar to C#'s Extension methods. That way consumers of the Refinement can opt-in to the extended functionality without injecting their methods globally into the class for every bit of code running on the VM.
There's a good reason that ActiveSupport as a "Monopoly on Monkeypatching" which is that nobody should be doing it any more. ActiveSupport largely gets a pass because it is pretty well defined and engineered, bits of it get pulled into core ruby, and its the 800# gorilla out there because it is rails.
You shouldn't copy that pattern, and you shouldn't want to. Use refinements instead.
Nobody who is greenfielding code should copy the activesupport pattern, though, and you aren't losing anything by being told not do that, you're being shown a better way.
I'm not really fussed about dictionary definition debates anyway. I generally use ActiveSupport in personal Ruby projects. It just adds too much value to ignore.
But these days the main thing I think of when I hear his name is that his blog used to be on a different domain, and he apparently let the old registration expire because someone else has bought it and now it points to a porn site. And tragically, there are still many other sites online which still link to Solnica's outdated, now-pornographic URLs.
And that's how I ended up accidentally sharing a porn link in a professional setting.
It reminds me a bit of MS's good old EEE strategy. People get comfortable with ActiveSupport to a degree where they start disliking to not have it's small benefits. But the consequence of this behaviour, that most are oblivious to, is that it to a large degree puts Rails in control of the language.
ActiveSupport feels like a leftover from the old days of prototype and jQuery. You search for a solution and half the answers on SO will assume that you are using it (and many of the answer authors don't even know it)
I'm sure it's not going to happen, but I think Rails should stop using ActiveSupport internally. It would allow devs to decide for themselves if they want to pollute the standard library, and also open the door for competition.
While the syntactic sugar of "1.day.ago" is hard to match, I don't think an alternative have to be that bad "ActiveTime.days_ago(1)"
Prototype added a whole bunch of new methods to the built-in JavaScript objects. It also defined $ as an alias for document.getElementById.
jQuery added nothing to the built-in objects, but did also use $ as an alias for the jQuery function.
Something I really liked about jQuery was that it included a noConflict() function, which you could use to avoid it interfering with Prototype. Doing this:
jQuery.noConflict();
Reset the $ back to whatever it was before jQuery was loaded.If you wanted to keep using $ in your jQuery code you could use this elegant idiom, which registers your code to run when the page content has loaded:
jQuery(function($) {
// Within this block $ is an alias of jQuery
});"1.day.ago" is not really a piece of code, it's a sensibly named shortcut to existing code that already solves easy problems. It's is not designed to be built upon; not all code has to be designed that way.
For example:
// How to get last day of month?
// Javascript
var month = 0; // January
var d = new Date(2008, month + 1, 0);
console.log(d.toString()); // last day in January
// monkey patching in ActiveSupport
Date.today.end_of_month
This is the definition of a great DSL. It abstracts away easy problems so you don't have to solve them hundreds of times and allows you to focus on the more unique problems of a domain.
Monkey patching is a powerful tool and just like any powerful tool it should be used carefully. Random monkey patching everywhere is bad, just like polluting the global namespace in javascript is bad. But used well and you can create extremely expressive, powerful, and concise coding experiences. Isn't that the goal of any DSL?
D, Go, C#, Rust, Swift, Kotlin, Scala, Haskell, and (more I'm forgetting) already have this without the hack of monkey patching.
If you really find beauty in DSL's you should look into tagless final encoding in Haskell.
From what I recall Ruby still doesn't have first-class ADT's. Meanwhile Java will be getting sealed classes (the OO orthogonal to ADTs) with pattern matching.
By DSL, I meant an abstract language on top of an existing programming language that serves a domain well and Ruby's monkey patching makes that easy to implement.
Haskell's language, with it's custom non English symbols, and functional language with verb object or even operator at the end style... it's like dvorak vs qwerty. Even if it is superior, it's not the preferred one.
``` using Dates julia> lastdayofmonth(today()) 2022-02-28 ```
But that's function oriented programming for you.
I had the luck to work with one of the Rails Devs in my work place and really learned to love it, the thing that makes rails great really is the structure.
Taking over a 10 year old rails project written by a newbie? Well not to big of a problem. Taking over the same project by someone who knows rails is always a pleasure. Like you know exactly where a bit of code would be found, it doesn't feel like taking over legacy depts but like a fun new adventure to explore.
As web dev who used to work with PHP before that was mind blowing. In the PHP world every bit of code was different enough that everything 'legacy' was something nobody wanted to work with.
It's less about the dialect but the smart structure and MVC
Or you can cast &object to &Trait.
Solnic's complaint is often voiced, and has merit for all the reasons given (and more besides IMO), and Rails is certainly stuck with ActiveSupport, but the core team does not seem keen on exacerbating the problem.
Additions to ext modules are, consequently, rare, and in fact there's a trend going the other way, e.g. https://github.com/rails/rails/commit/33ca3923170a50da7f1731...
This type of shenanigans always rubbed me the wrong way. Like, are we saying that "1" has a property, "day"? Conceptually that makes no sense, right? And "day" having a property "ago" is bogus as well.
So then, is it just a cute "let's make it read like a sentence" trick?
One time a colleague was trying to tell me about this trick, and I opened up a ruby interpreter to see what he was talking about. I typed in "1.day.ago" and pressed enter and it gave some error message. A day later he came back and told me how he was surprised to learn that the trick only worked if you had ActiveRecord in the mix.
To me, that's all downside. Sneaky and confusing that loading an ORM adds magic properties to your _numbers_. And, the properties don't even make sense.
The worst part of it all is: how in God's name are you supposed to find out where these magic properties and methods sprinkled all up into everywhere -- where did they come from, so you might read their docs, or read their source? It's madness!
number.day -> timedelta
timedelta.ago -> current time minus delta
2.months.from_now
3.months.after 2.days.ago
1.day + 2.weeks
Given the choice between Time.now.advance(years: 2)
and 2.years.from_now
in a language that uses idioms like 1.step(to: 10) {|x| print x }
I think it's totally natural.Abstract of any programming language, the concept of "2" has the properties that it is an integer, that it is a natural number, that it follows "1", that it is prime, etc. But it doesn't have a property "years".
From another perspective, back to programming, if "years" and "step" make sense as properties of "2" then does _any noun_ make sense? Is it cool for every library to monkey patch its own domain's worth of nouns to be properties of every number? 2.centimeters, 2.users, 2.widgets?
I can get with natural expressions for date math, but how about something more like
Time("2 years from now")
Then I can read that to mean we are constructing a time, described by the parameter. 1.class.methods
will give a long list of methods supported by `1`This is because in Ruby everything* is an object. I agree that from outside Ruby this might look weird, but knowing Ruby this is very simple to understand.
Knowing this you can also expect the following things to work in Rails I think:
1.day.ago + 2.hours
This is because:+ is an instance method defined on the Integer class. See Integer.instance_methods. So it is more like `1.day.ago.+(2.hours)`
and I expect then that in Rails (or some gem) they added `hours` as a method to the Integer or a compatible object
so in the end this operation is just normal:
object.instance_method.instance_method(object.instance_method)
nothing magic here happening because this is all valid OOP code where an instance method could return a self object let's say.Knowing this then before writing in a Ruby Interpreter one can find out if that code is in the standard library (see https://ruby-doc.org/core-3.1.0/Integer.html) or in a Gem
On finding out where a method came from, I appreciate that I can look to see if it's core or else must be from a gem, but I want to know _which_ gem did it come from.
Maybe in the case of a language with a very small standard library, this could be easy: as the best assumption could be that it is in a plugin/module. But I think looking at a piece of code and direct knowing from _which_ plugin some methods comes from has more to do with naming conventions than with the language itself, especially in languages where either overriding or overloading or a form of them is possible.
In the case of Ruby, I recommend RubyMine and just to go to definition. 99% of cases it will correctly open the definition from the gem of std library.
I agree that in Ruby - maybe more than in other languages - one can really make some behaviors hard to discover. But most of the libraries are straight forward and Rails is very well documented in code. I usually prefer to just go to the definition and read the method than browse the API docs.
This isn't unique to Ruby and is possible in other languages.
For eg. in Smalltalk you could create this DSL...
1 day ago.I really wish i will finally remember this next time and i will spend the time coding hapily instead, sometimes with ActiveSupport, sometimes not, maybe even with Dry-whatever, if i'm able to overcome Solnic's negative agendas.
I want Ruby ecosystem, where everybody is free to do their things the way they want, by the rules they want, for whatever motivations they have. I want to have my special Rails snowflake and i don't want it to ever change because it constantly enables me to build amazing things AND enjoy the process also. And i'm willing to let you have your way and i will respect it but only if you can do the same without being negative about it in every other statement.
If somebody chooses to support Rails and Rails only in their library i think it's very fine and i think the same if there is a library doing the same thing for only Hanami. Or a library supporting both. Or a library not really supporting anything in a special way. I also think it's fine when there is a library which sole function is to monkey patch Object with #forty_two method. And fine it is because there are no rules from you or anybody else forcing me to actually use this thing. So, i will just not use it, right?
So for me this open and free "we" is the only "we" there is and also the only one i want to evolve anyhow.
I provide critique that some (probably many) Ruby developers don't like and disagree with but my intention isn't to bash something and make it "all bad". I don't recall saying that everything involving Rails is bad and wrong. I had conference talks sharing my appreciation for Rails and various parts of its philosophy that I agree with and try to incorporate in my own projects. There are also things I don't like that are very problematic and I share this with the community as well.
> I want Ruby ecosystem, where everybody is free to do their things the way they want, by the rules they want, for whatever motivations they have.
Imagine a ruby gem that becomes super popular because it provides some neat features that plenty of companies can benefit from. Then, for whatever reason, this project introduces some great performance optimizations but they are achieved through monkeypatches. Unfortunately it turns out it breaks Rails in some weird, subtle ways, that are hard to debug. Rails core teams starts getting lots of bug reports about some seemingly unrelated problems, just to realize that it's not a problem in Rails, it's a problem in another library.
Does this make any sense? Nope. That's why people don't monkeypatch in their libraries.
From what I gathered, there's some initiative to move Rails to use Refinements via ActiveSupport, if this happens, then the problem is solved.
> And i'm willing to let you have your way and i will respect it but only if you can do the same without being negative about it in every other statement.
It's interesting that you mention respect here - ActiveSupport doesn't respect the rest of the ecosystem. By simply adding new methods to Kernel, Object, NilClass, it reserves them and you really want to avoid having same methods in your library. If you want to tell me "but nothing prevents you from doing this" then you're right, except that I have to think about this because if my lib doesn't work with Rails well, I might as well not build it in the first place because adoption would be non-existant.
Yes, maybe that is the thing, i don't like critics. The approach i prefer is to put your money where your mouth is. Why don't you to build this version of ActiveRecord using refinements? Then let others see how good it is
> Imagine a ruby gem that ... Does this make any sense? Nope. That's why people don't monkeypatch in their libraries.
This can happen any day because of infinite types of mistakes a programmer can make. Monkey patching is just one technique you have at your disposal and as with everything else, you have to know when to use it, how and why.
> It's interesting that you mention respect here - ActiveSupport doesn't respect ...
What do you mean doesn't respect? ActiveSupport is a tool without feelings and stances and believes. You can either use it or not use it. And actually i want ActiveSupport to do exactly what it does, patch all the things and give me tons of nice methods i need.
Yeah, so if you want your library to work with Rails, because *you want* Rails users adopt your lib, you have to make some effort right? Well that's how it works with anything. If you don't like it for whatever reason why don't you find some place where they do it exactly your way, or why don't you build it? I mean i like the way how Rails and the other things around work and that's why i use it. So if you want adoptions for your library from me, you have to kind of a do it the same way.
"Dialect" implies that it's a sub-language, but Ruby is still Ruby no matter how many methods you add or modify.
Whereas a "Jargon" is a specialized vocabulary, and even sometimes some word change meaning as part of a jargon.
But I suppose that when you title your post like this, controversy is actually what you are looking for.
It's that which (the author argues) makes it a dialect, rather than a specific jargon.
The instances of abuse are generally less in mainstream JS libraries, and in many of them such features are usually opt-in or at-least it is possible to still use the lib even when opting out , there is also no major dialect that has privileged rights so to speak.
And it's mostly fine, but the cavalier attitude to meta programming and lack of namespacing is a problem. And not just convenient (but ultimately insane) stuff like 1.second.ago - but also in core rails. On a current project interfacing with a legacy database, I've encountered tables with columns named "table_name" and "changes" - both of which are shadowed by ActiveRecord itself.
But even with the crazy, it does make starting a project right, quite easy. Tests are set up, and with rails 7 you can finally opt out of most of the crazy js/css tooling and still have a nice front and backend stack.
When giving directions, we don't say: "please proceed forward for 17.84 ft, while reducing your speed, come to a complete stop, and then gently accelerate while turning 90 degree." We just say: "go to the corner and turn right."
Certain abstractions and improvements to the way we communicate make a lot of sense. Why fight it? Sounds like the author has the perfect mix of skills to improve the core of Ruby to a point where it's useful by the rest of us who don't know and don't have the time.
In the US, it’s like trying to talk to someone from the deep south versus Maine.
Imho there isn't a good reason for Rails to depend on the monkey patched ActiveSupport parts and it could be factored out into less magicky functions inside Rails.
[0]: https://rom-rb.org/
I think DHH's aesthetic intention with ActiveSupport was for it to be used by Rails, not used by a random assortment of gems throughout the Ruby ecosystem and misunderstood by a lot of people.
FWIW, ActiveSupport is one of the main reasons why I stopped using Rails a while ago. The other was that version updates typically have a LOT of API changes that make staying up to date quite a drag (or source of tech debt).
Not sure this is what you're getting at. I agree with the author though, this aspect of Rails bothered me quite a bit. Knowing that working on a Rails project meant I could magically generate ranges from dates because of ActiveSupport was unsettling - it made me wonder what else was non-native behaviour and waiting to trip me.
I totally recognize that it's also a strength of ruby. I really enjoyed working with it. It's super expressive and clear to read. ActiveSupport is all really well documented and designed too, and I'm not trying to criticize it.
After re-reading my comment I'm noticing I was specifically targeting Rails as well. I think if anything Rails does a great job and shows why this is a cool thing about ruby. I think what I should have said is that this aspect of ruby is what I found off-putting and specifically in a context where I'm relying on a vast number of dependencies, coworkers of various skill levels, and shipping code to customers. Does that make more sense? I didn't express what I was thinking very well before.
I'm a lot happier with something like Rust, and it arguably comes down to personal preference. Ruby is awesome and fun to write. I'm not a good enough programmer to make it as productive as others in the long term though, especially when working on a large team.