0

Given:

create_table(:foos) do
  primary_key(:id)
  String(:name)
end

create_table(:bars) do
  primary_key(:id)
  String(:name)
end

create_table(:foos_bars) do
  primary_key(:id)
  foreign_key(:foo_id, :foos)
  foreign_key(:bar_id, :bars)
  String(:name)
end

class Foos < ROM::Relation[:sql]
  dataset :foos

  def with_bars(id)
    prefix('foos').qualified.select(
      :foos__id, :foos__name
    ).select_append(
      :bars__id, :bars__name
    ).left_join(
      :foos_bars, foos_bars__bars_id: :foos__id
    ).left_join(
      :bars, bars__id: :foos_bars__bars_id
    ).where(foos__id: id)
  end
end

class FoosModel
  include Virtus.model

  attribute :id
  attribute :name
  attribute :bars
end

class BarsModel
  include Virtus.model

  attribute :id
  attribute :name
  attribute :foos
end

I've tried many, many random variations on the keywords shown in (but not explained in) the ROM docs to no avail. Here's a literal interpretation of the docs into a mapper for Foos, which doesn't work:

class FoosMapper < ROM::Mapper
  relation :foos
  register_as :foos
  model Foo

  prefix('foos')
  attribute :id
  attribute :name

  group :bars do
    model Bar
    attribute :id, from: :bar
    attribute :name, from: :bar
  end
end

How does one write a mapper (or rework the relation to work with the mapper) to get the simple result of a foo with a bars attribute having all the bars linked by the foos_bars table?

Yuri Gadow
  • 1,814
  • 2
  • 16
  • 26
  • Is this for Ruby on Rails or just a stand-alone Ruby program? – erapert Nov 11 '15 at 00:20
  • This is in Rails, though I can't say I've seen much integration between ROM and Rails—a good thing in my mind—so I'd be a little surprised if the pool of possible answers was impacted. – Yuri Gadow Nov 11 '15 at 00:26

1 Answers1

1

Building up complex joins like that is not recommended unless you have good reasons like performance. It's much simpler to use repositories to compose relations by defining relation views that you need for reusability and composing those in various way inside repos. Defining custom mappers should also be avoided unless you have some unique requirements.

I made a gist that illustrates that right here: https://gist.github.com/solnic/9307e1b2e3428718dd12

We're working on a new set of docs that will properly explain those things.

solnic
  • 5,733
  • 2
  • 23
  • 19
  • Thanks for the alternative! Unfortunately I think we do need a mapper as the data needs to end up in custom objects that have some domain logic. For what little it's worth, I disagree removing one line from the join and then adding the lengthier code, complexity, and inscrutability of a repository-combine is simpler than the common idiom of query-and-map-the-graph. – Yuri Gadow Nov 11 '15 at 18:03
  • Eager loading is actually much more simpler than building a big result set from multiple joins and mapping that into aggregates. I can show you an example of a custom mapper that would do this personally I don't agree this is simpler. Repository combines are pretty simple, and keeping relation views separate gives you much better flexibility and composability. – solnic Nov 11 '15 at 18:50
  • oh one more thing, next version of repo will have ability to map to your own objects via `users.as(MyUser)`. it's already in master – solnic Nov 11 '15 at 18:50
  • I'm sure you're right about the complexity under the hood, I was speaking only from the API user's perspective, and even there I'm likely wrong—hard to say until I can get something, dear god anything, to actually work. :) I will give repositories another go today and hope I can dodge the base name issue. – Yuri Gadow Nov 11 '15 at 19:14