I never found anything wrong with Active Record patterns, in all the projects I worked with. Of course, it depends on the ORM and it's a pattern that blends itself especially well with dynamic languages (IMHO, Hibernate sucks).
Of course an ORM will have SQL concepts because that's the nature of the beast. You can't abstract those away.
There is also nothing wrong with generating SQL for you, as generating SQL by hand is a task that involves string concatenation. And when adding filters to that SQL based on lots of runtime conditions, it gets freaking painful to do it - it's a lot nicer to work with expressions that are building a syntax tree, to which you can add and add whatever you need.
Also, about efficiency, the dumb ORM in Django can do this (prefetching relationships on one level):
Which returns an array of (group, name) tuples. No inefficiency there and no leaking abstraction either.
You can also drop to pure SQL, if you're so inclined for efficiency reasons. In that SQL you can also restrict the columns you're selecting, and so the model objects returned will lazily load missing attributes on request ...
user = User.objects.raw('select id, username from auth_user limit 1')[0]
# already retrieved
user.username
# this works, but a second query is made
user.first_name
So, yeah, people can use raw SQL or whatever fad-du-jour they want. I'll just be busy working on stuff.
The way I think about it, active record is the strong option for crud, but doesn't scale well past it.
Once you're dealing with many tables I think you need the ability to accumulate a graph representing a new state and then commit it as a transaction.
While you could do this in an adhoc manner with active record, it's not a way of life in the way it is in EOF/Cayenne.
There are complications of the object to relational mapping that it doesn't deal with. For example - no mechanism for dealing with situations where someone change data underneath the object graph a user is working against in memory. AR would blast over the top of the data with what it has in memory, potentially breaking in the process.
Some ORM systems have locking levels. For example, with 'optimistic locking' you get an exception if data changed underneath you. It's handy to be able to catch an exception, email the support team, and tell the user to freeze and contact them, in a way that doesn't discard the data they've just entered.
I find it backwards that active record exposes methods like insert, delete, update. These are low-level sql concepts, and you shouldn't need to care about them within an object system where you're thinking in terms of a graph of data. It's very wired to the database though. As far as a "mapping" between object and relational, active record is crudely simple.
On a positive note, you can hammer an AR out in your language of choice from memory, without reference to any external libraries or the like. It's a good wrench in the toolbox.
Yeah, but the relational model is terrible for representing graphs, no matter the tools you use, you are still bound by the representation you choose in a relational database.
What I like about AR is precisely its close correspondence with a relational database. Objects have create/save because they correspond to DB-tables and that's not something I want to forget when working with such a database. Personally I want to control when a specific object gets persisted, as to me that point is crucial, both for performance and for data integrity.
When the relationships between tables get too complex, that's a good time to reevaluate your data-structures and how you process that data -- as in, do you really need a live graph inside your requests, or will a pre-processed view will do, while having hooks for inserting new data and rebuilding your models asynchronously (on a single thread to avoid race conditions)?
Also, there are some databases around that are specialized on storing graphs. Even if the relational model is very general-purpose, sometimes you're better off choosing a system that's more suited for your goals.
a mechanism for dealing with situations where
someone change data underneath the object graph
a user is working against in memory
I kind of understand what you're saying, but you should avoid sharing of resources like that. Choose stateless requests, even when building non-web apps.
Do not let users trip over each other and if you do, construct a trail of actions taken to be able to Undo (as in, the Command pattern).
It clarifies for me that the goals of Active Record and other forms of ORM are so different that it's misleading to categorise them together. Active Record presents a simple and unambiguous interface to the way things are, [other systems that I don't have a noun for] try to abstract it away.
The way we use databases is interesting. In the early days of computing, a lot of the low hanging fruit was reached via software that wrapped databases. As a result, the database tools reached awesome power, maturity, and mindshare. As a result, they now get used for things well outside of their domain.
I suspect there are patterns that get squeezed out by this, and it's a topic I'm interested in exploring.
You described the reasons I stuck with my own psuedo ActiveRecord on top of NHibernate's basic Transaction and Session support. It's called "unit of work" and it makes much more sense to me. We're very specific (for simplicity and sanity) that all HTTP operations to our webapp are autonomous (from the user perspective) and stateless. No multipaged forms, etc.
So every request is given a session and whatever needs to happen as a result of that request is encapsulated in that session. If something goes wrong, that transaction in that session can be reverted. If NHibernate determines that it needs data modified early in the session for something later in the transaction, it handles it behind the scenes. Then an ActiveRecord pattern can be thrown on top of that and you still get CRUD within a "unit of work" context.
Of course an ORM will have SQL concepts because that's the nature of the beast. You can't abstract those away.
There is also nothing wrong with generating SQL for you, as generating SQL by hand is a task that involves string concatenation. And when adding filters to that SQL based on lots of runtime conditions, it gets freaking painful to do it - it's a lot nicer to work with expressions that are building a syntax tree, to which you can add and add whatever you need.
Also, about efficiency, the dumb ORM in Django can do this (prefetching relationships on one level):
It can also do this: Which returns an array of (group, name) tuples. No inefficiency there and no leaking abstraction either.You can also drop to pure SQL, if you're so inclined for efficiency reasons. In that SQL you can also restrict the columns you're selecting, and so the model objects returned will lazily load missing attributes on request ...
So, yeah, people can use raw SQL or whatever fad-du-jour they want. I'll just be busy working on stuff.