It must also be a massive boon for the Rails ecosystem to have such a large property running off the head.
Doesn't anyone know of any Django shops that do the same, running off mainline?
It must also be a massive boon for the Rails ecosystem to have such a large property running off the head.
Doesn't anyone know of any Django shops that do the same, running off mainline?
I'm new to Ruby, with 13 yoe as a SW engineer.
Personally, I find it a very hard language to master. Writing tests often feels like I'm settings variables left and right without seeing them being used in the current context. But that then happens to be part of the let() way in rspec.
Now you might say: why use rspec? I inherited this codebase, so gotta do what you gotta do.
I do really miss my compiler. I am not a fan of writing a block somewhere that can be invoked 2 weeks later for the first time and then fail, because someone passed in a number where a string was expected.
I went from PHP to Java to C# to F# to JS to Rust. F# and Rust stand out in terms of hardest to write, but easiest to trust.
I don't have that feeling with Ruby, and RoR.
But, again, my personal opinion. Good friend of mine started with Ruby, and he loves it. He says that he accepts the magic things for what they are, and uses them. My brain doesn't allow that. I have to understand.
I hear you on the compilers, and I do miss them at least a little whenever I don't have them. But my goodness, there's really something fun in Ruby. I think it shines in smaller codebases, situations where you can have some hope of understanding 90% of everything that's going on as a single developer (and honestly part of the sales pitch of microservices is that you know there's no funny business crossing the wire, so you only need to know the magic bits of the services you work in). But there's really so much joy in stringing together a few functions in a single line, knowing that there might be a few different types being passed around depending on context but knowing that all the functions can handle all the types, and not pausing after every line to check whether an err was nil... Honestly when I have a compiler i miss the magic, and when I have the magic I miss the compiler.
How are you supposed to fix a bug in your own service if you don't know how to set up a specific permutation of services locally in order to reproduce it?
or maybe put it this way: the big-ball-of-mud formed by metaprogramming tricks feels like it has boundaries that are permeable in a particularly arcane way, whereas the big ball of mud formed by microservices has boundaries that are permeable "merely" by distributing or spanning of both humans and logic.
Good metaprogramming levels up a language's power in the same way going from a hammer to a nail gun levels up your building power.
Creating a DSL in ruby is mostly just creating well named class methods, often that take blocks, and calling them without parens. Sprinkle in a little `method_missing` overrides and a few `define_methods` and that's it.
I don't know why people seem to actively avoid learning it, but a day of study, and stepping through a few "magic" methods with a debugger should reveal how simple and non-mysterious it is. And also how useful it is.
Metaprogramming is pretty amazing for gems and DSLs in a limited domain. When I see business logic that has metaprogramming in it, I kinda freak. I know in the future that business logic will change and debugging the very clever code in that business logic may become a nightmare.
I say this a person who adores Ruby.
A lot of stuff uses method_missing and define_method. There’s also instance_eval, which is used to create DSLs like rspec. Once you know what they do, stuff makes a lot more sense.
[5] pry(main)> $ user.first_name
From: /Users/.../.asdf/installs/ruby/2.7.7/lib/ruby/gems/2.7.0/gems/activerecord-
6.0.6/lib/active_record/attribute_methods/read.rb:15:
Owner: User::GeneratedAttributeMethods
Visibility: public
Signature: first_name()
Number of lines: 4
def #{temp_method_name}
name = #{attr_name_expr}
_read_attribute(name) { |n| missing_attribute(n, caller) }
end
Tells me exactly where this "magic" method is defined, and I can pop open that file and read all the source.Problem solved by using Sorbet in your codebase. I know at least Stripe and Shopify do. Also forces you to keep magic at the minimum (which any sane dev would do anyway).
Ofc, you'll shoot yourself in the foot doing that if you let individual test files get to big, but that's a bad idea anyway sooo.
In terms of the "I wrote a block somewhere and it broke when executed for the first time two weeks later" - don't do that in a scripting language. Use the REPL to build/test the block. That's the trade off - instead of a compiler you get a fast REPL - use it! :)
Maybe I’ll check back in 3 years? But it seems to be A pet toy of Shopify, and for their needs.
TS is a bit more flexible and expressive than Sorbet, but I find Sorbet very ergonomic even with strict typing. I rarely have to use T.let or T.must.
Sorbet's typed data structures like T::Struct and T::Enum are also great.
instance_variable_defined?
defined?
are two common onesbut if you are using something before defining it you are going to crash so that's definitely one way of distinguishing it.
using fetch is another way to provide a default value to something that may have a nil value as a meaningful value.
> but if you are using something before defining it you are going to crash so that's definitely one way of distinguishing it.
Would you clarify? Like that was my point, casually using something where `nil` is a possible assigned value, or maybe the variable hasn't been defined, makes possible the (sadly common) category of bugs where the program does not crash, but proceeds as though the variable was assigned `nil`, but actually the variable was never defined.
In contrast to local variables where the program will crash if you reference the variable but it hasn't been defined.
My comment meant "bare instance variables in Ruby are not great [and we might not want to recommend them as a solution to people complaining about Ruby/Rails quirks]". Do you disagree and actually love the behavior of Ruby instance variables? Or are you simply technically correcting me? (which, again, I appreciate)
yeah I wouldn't say i love it and i'm not sure why they did that but i imagine they had a reason for it originally. i would say it should have crashed instead of return nil as a default. they did tend to try to make the programmer happier and maybe they did it to that end but i don't see a large upside to it... not too much we can do about not using instance variables though... at the end of the day you just have to be a little more careful.
tests are a pretty good thing to have though and can catch this kind of error.
Contrast to the JS ecosystem where as soon as there’s a disagreement or new idea a new framework is born
Every backend lanaguage has one or two really good frameworks. Simply because the cost of replacement is so high and the cost of developing a new one is so high, it just makes sense to invest in the one you're using. Sadly, this means just investing in hiring more people and not in actually improving it which GitHub/Microsoft is doing.
That's more because of our engineering culture. The cost of NOT upgrading outweighs by a huge margin than keeping building on top.
And yes, have seen Django shops locked into 0.9x release patched right into the core and running for a very long time, impossible to upgrade and all the horror stories.
EDIT: Added Django
Sounds like this is a good question to ask companies when you're interviewing, since it probably can act as a proxy for a lot of other engineering habits.
I found framework and runtime "freshness" to be a good metric of company engineering culture.
For example:
- Good test coverage (particularly for critical points of your application) including CI pipeline
- Use dependabot or similar to make sure you get alerts for any critical updates (like security patches)
- Practice good dependency "hygiene" - only add a dependency when you really need it, and do vetting to ensure it's well-supported, has been updated recently, etc.
- Regularly audit and remove dependencies - when a library author declares they are sunsetting their project, plan on a replacement (even if that is, worst case, a fork). Remove dependencies which are no longer used in your code.
- Separate out your development/testing/production dependencies
- Use a good dependency manager: for example in Python, use Poetry or pip-tools or similar, rather than manually updating requirements.txt files yourself
- Update early and often: other than dependabot make it a maintenance task to check for updates at least once a week. Use managers, scripts etc to make this as easy and painless as possible.
Obviously if you are inheriting a legacy project you have to deal with the cards you are dealt, but these are a good target to move towards even with an old codebase.
It took Github a full eight years to get caught up after that fateful decision in 2010 to hold off on Rails 3.
I can understand the reason to put off encrypting the field too. Upgrades, or backporting, are just work like any other project and I guess that other things for now take preference. After all, before field encryption came along the risk balance calculation had already been done (even if only implicitly) that storing the field unencrypted in the database was safe enough. The availability of encrypted fields doesn't change that, though making some things safer doesn't necessarily fall neatly into the "requirements" bucket despite being a good idea.
The cost of not upgrading is always the worst option. It's just that those making the decisions might not have the same stakes. It has nothing to do with technology and happens in every situation.
Often you're just not even aware of what these costs are because you're wired to work around it. It just feels right to continue that workaround including continuing to hire and expand when it reality it's just more overhead.
Consider moving to an SOA. Rather than upgrading a monolith when the engineering team knows that it’ll gradually have pieces pulled out of it, use that effort on the migration.
I would have expected Microsoft to focus on developer efforts into speeding up Ruby as a language given they are one of a small few large companies that have deep language/compiler expertise.
Not in my experience.
Is rails the same way dependency wise?
I consider that a better measure of dependency risk than absolute count, since different ecosystems have different ideas about how large a library should be.
The brand new react docs don't even mention it: https://react.dev/learn/start-a-new-react-project
> We are currently leaning towards Option 5 ("Turn Create React App into a launcher"). The original goal of Create React App was to provide the best way to start a new React web app for the majority of React users. We like that repurposing it as a launcher explicitly communicates a shift in what we think is best for most new web apps, while at the same time leaving an escape hatch for the older workflows. Unlike Option 3, it avoids the perception that "creating a React app" is somehow deprecated. It acknowledges the reality that a bit of choice is needed, but there are now really great options to choose from.
https://github.com/reactjs/react.dev/pull/5487#issuecomment-...
Existing Interface getting removed OR behavior or defaults changing for an already existing Interface.
That's usually the upgrades are about.
It is quite independent. There are between two and four dependencies: asgiref, sqlparse, tzdata on Windows only [0], and typing_extensions on <3.11 [1]. There are some optional dependencies (argon2-cffi, bcrypt, and a database library like psycopg2), but they are small and mostly self-contained.
[0] https://github.com/django/django/blob/main/setup.cfg#L39-L42 [1] https://github.com/django/asgiref/blob/main/setup.cfg#L34-L3...
For example you need an external package to use builtin postgresql, mysql or oracledb support. You need tblib to run tests in parallel using the built-in test runner. You need external packages to use argon2 or bcrypt as password hashers in the builtin auth system, and I'm sure there are others, since Django is very much batteries included, but modular.
The modularity also means you can also use any 3rd party database, use pytest to run tests, make your password hashing on your own, ... so I can understand that these are not listed as dependencies on pypi, but I don't really like that I have to list packages I don't directly import in my code as direct dependencies...
The first release of Django was in 2005. Back then, the Python Package Index didn't exist yet. Installing Python dependencies was really hard - you pretty much had to grab a copy of the code for each one and put it on your "sys.path" somehow.
So Django avoided the issue entirely by bundling everything you needed to build a web application in a single package.
That's why Django has "django.contrib" - in a time before pip dependencies, it was a way to separate out things like GeoDjango which weren't exactly part of the "core" framework but could be distributed along with it.
django does not use sqlalchemy as its ORM, it has its own system (which i prefer!).
I have tried a lot and the least worst is Zapatos (and it's not really an ORM) because it at least tries to not paper over SQL and instead just creates a type-safe API for using SQL.
Now, if someone could create a Django-like ORM for Java or Rust, then we're talking. Hibernate and Diesel are nowhere near Django's ORM in terms of productivity and "it just works" factor. Go's GORM looks pretty good but haven't tried it. jooq does not look as easy to use or setup, and the workflow is entirely different.
Nowadays most projects I've seen use the latest stable release or at least the latest LTS - maybe some legacy projects lagging behind.