> Objects map neatly to records
This is plain wrong. The problem of mapping records (presumably you mean database records which are actually tuples but lets call them records) in a relational database to objects in an object hierarchy is the source of the "object-relational mismatch" wherein the fundamentally graph-based structure of an object model is intrinsically incompatible with the related sets-of-tuples structure of a relational database.
To try to solve this problem, an inordinate amount of time has been put over many years to develop various ORMs (Object-Relational Mappers) which try to hide this intrinsic incompatibility under many layers of abstraction.
A whole new industry came out of the simple fact that people who had been lead into thinking that OOP was the only solution to any problem wanted to use relational databases to back their objects.
These ORMs all come with innumerable caveats and shortcomings. Their abstractions constantly leak and their performance is a constant firefight between making code idomatic OOP and making code actually perform in a reasonable way.
To claim that objects map neatly to database records is completely ignorant of even the history of how ORMs came about.
Your comparison of `user.blocked_by? other_user` to `is_blocked(user, other_user)` is a complete red herring anyway. Nothing stops a non-OO type system from letting people specify that a function applies to a type such that you can write `foo.is_blocked_by(bar)`. Nothing also stops you from writing code more explicitly and clearly such as: `foo in user_blocklist_of(bar)` or even mixing both concepts and writing `foo in bar.blocklist()`. In summary, these are all questions of syntax and style and have nothing to do with OOP.
> The reason it's good is that it is productive.
This implies that somehow all other methods of programming are less productive?
When I have the rare occasion where I need to write some kind of web application I reach for three things: Flask, SQLAlchemy Core and sqlite3. I design the database then design the application around it. By actually directly interacting with sqlite3 using a query generator like SQLAlchemy Core I get control over and the ability to use the features of my database of choice. I am no longer stuck trying to fix query performance issues by randomly mangling an object model until the resulting database interactions act like I want them to.
This to me seems far more productive than any of my experiences dealing with ORM performance issues and abstraction leaks.
> Who has blocked whom is more natural to talk about when there is a primary object subjecting the other object to a test.
I think there's nothing about this particular example which puts particular emphasis on either side of the block as the "subject" and the other side as the "object".
- a blocks b
- b is blocked by a
There's nothing about either of these which stands out as the correct mental model.
In reality it's not a subject-object relationship it's just a relationship between two subjects.
In a database this would be stored as a table containing a 1:many relationship between user and user.
By attaching this relationship strictly to one side of the relationship you've actually ignored the case where it may be useful take the perspective of the other side of the relationship.
If you end up factoring that into your object model you will presumably end up having two functions: `.is_blocked_by` and `.is_blocking`. These will likely have to have two separate implementations (although I'm sure you can get the ORM to handle that for you). (Also, don't forget the performance impact that you're likely dealing with WHOLE user records at once and relying on some lazy loading specification which you probably defined too loosely to be useful to avoid loading the entire "other user" from the database just to check if you're blocking them.)
I think now it should be clear that the single separate function approach is actually far more representative of the reality of the relationship.