-
-
Save stevenharman/1457584 to your computer and use it in GitHub Desktop.
| # This is a record, as in a vinyl. | |
| class Record < ActiveRecord::Base | |
| belongs_to :user | |
| belongs_to :album | |
| # explicitly stating what is needed | |
| def from_library_for_album(collector, album) | |
| where(user_id: collector).where(album_id: album) | |
| end | |
| # or, if we go more generic/idiomatic | |
| def from_library(collector, options={}) | |
| album = options.fetch(:album) { false } | |
| status = options.fetch(:status) { :mint } | |
| records = where(user_id: collector) | |
| records = records.where(album_id: album) if album | |
| records = records.where(status: status) if status | |
| records | |
| end | |
| end | |
| class Album < ActiveRecord::Base | |
| has_many :records | |
| belongs_to :artist | |
| end | |
| class User < ActiveRecord::Base | |
| has_many :records | |
| end | |
| # I prefer to keep AR as a private interface used only w/in the Model. | |
| class Library | |
| def initialize(collector, fetch_records=Record) | |
| @collector = collector | |
| @fetch_records = fetch_records | |
| end | |
| def fetch_records_for_album(album) | |
| fetch_records.from_library_for_album(collector, album) | |
| # ... or ... | |
| fetch_records.from_library(collector, album: album) | |
| # and what about more params? | |
| fetch_records.from_library_for_album_in_mint_condition(collector, album) | |
| # ... or ... | |
| fetch_records.from_library(collector, album: album, condition: :mint) | |
| end | |
| end | |
| class LibrariesController < ApplicationController | |
| def album | |
| library = Library.new(current_user) | |
| @album = Album.find(params[:id]) | |
| @records = library.fetch_records_for_album(@album) | |
| end | |
| end |
@steveklabnik,
Agreed. For this particular app, I really considered going with a Data Mapper approach, but in the end the thought of all of the friction scared me into sticking with AR. Perhaps, in hindsight, the friction I'm feeling in my design w/AR is MORE than would have been felt by going with a different ORM.
Fucking software, it's hard, man.
Fucking software, it's hard, man.
Word. Datamapper is okay, but the Ruby implementation tries to pretend it's AR too much, it has the same problems.
I don't have the time and energy to write a good ORM...
I feel like the DCI pattern could help you here... similar to your last code posting. (As we just discussed offline, but for posterity.)
Let data objects be primarily data objects. AR does an OK job with that anyway. That's the D.
The C would be your use case (I like the term activity better, but anyway). C is for context.
I are your interactions that you extend onto AR's domain/data objects at runtime inside your context.
The DCI pattern puts contextual analysis over the domain modelling desire "single source of truth," so I understand it's a different motivation but there might be more value in that POV?
There's some great discussion here, wish I could make it to CodeMash this year to partake in the booze filled continuation.
I agree AR definitely leaves a lot to be desired when it comes to separation of concerns. It's true that if you follow the idiomatic rules, things can remain quite testable purely because of the powerful stubbing capabilities in Ruby and RSpec. You don't have to wrap everything in a testable abstraction like we were all used to doing with C#.
But there are definitely things that still feel weird with AR and don't work quite as well if you try to build any abstraction layers around it. I'd consider Sequel as a possible alternative to using an ORM, although its really raw by comparison.
Or just try using MongoDB and get out of relational mapping all together. For side projects, I think its probably the best way to go.
Yeah, the real problem is that AR is kinda broken by design. Yet so many things are written to work with it, going with a lesser-used ORM can be rough.