Calculating rowspan in an HTML table of nested DataMapper resources
I have a legacy database that sits behind a really crappy PHP app. The web app is causing all sorts of problems with the database, and I just need a quick-and-dirty way of representing what is stored in the database in a way that my client can see how bad the problem is, So I built a Sinatra + DataMapper application, but I'm having trouble calculating some values in the view.
In the end, I want a table like this:
+++++++++++++++++++++++++++++++++++++++++++++
+ Profile + Registration + Signup + Payment +
+ + + +++++++++++
+ + + + Payment +
+ + ++++++++++++++++++++
+ + + Signup + Payment +
+ +++++++++++++++++++++++++++++++++++
+ + Registration + Signup + Payment +
+ + ++++++++++++++++++++
+ + + Signup + + <- Signup w/ no payments
+++++++++++++++++++++++++++++++++++++++++++++
+ Profile + Registration + Signup + Payment +
+ +++++++++++++++++++++++++++++++++++
+ + Registration + + + <- Registration w/ no Signups
+++++++++++++++++++++++++++++++++++++++++++++
+ Profile + + + + <- Profile w/ no Registrations
+++++++++++++++++++++++++++++++++++++++++++++
My first issue is that this requires a ton of SQL queries [basically (n+1)*(n+1)*(n+1)*(n+1)], but AFAIK there is no way to make DataMapper do eager loading of nested models, nor anyway to cache the data model in a way that makes reads more efficient. (I'm not writing anything back to the DB, and a snapshot of the database is fine - don't really care about live updates). This is tolerable for now because this is a 1-off report.
My Second issue is calculating the rowspan for the given columns. At first I was just using the number of payments for the current scope (profile.registrations.signups.payments.size
, registration.signups.payments.size
, etc.) but as you can see in the first example above, there is actually one less payment than what would be required to have an accurate rowspan for the profile.
The only theoretical solution that I've been able to come up with is to basically loop over each child object at every step along the way to find out exactly what children objects are available beyond that step, but that seems really inelegant and would require an exponential increase in the SQL being run.
Is there some other way that I can approach this? I'm not married to DataMapper or HAML, I just want to get this done as efficiently as possible.
I'm pasting my current HAML and data models below for reference.
index.haml (it's nasty, I know)
#content
%p #{@profiles.size} Profiles
%table.display{:border=>1, :cellpadding=>2,:width=>'100%'}
%thead
%th Prof ID
%th Profile
%th Reg's
%th Reg ID
%th Registration
%th Signups
%th Event ID
%th Event
%th Event Date
%th Payments
%th Pmt ID
%th Amt
%th Pmt Date
%tbody
- @profiles.each do |profile|
- profile_rowspan = [profile.registrations.signups.payments.size, profile.registrations.signups.size, profile.registrations.size, 1].detect { |i| i > 0 }
%tr{:valign=>'top'}
%td{:rowspan => profile_rowspan, :class => "profile"} #{profile.id}
%td{:rowspan => profile_rowspan} #{profile.firstname} #{profile.lastname}
%td{:ro开发者_如何学Pythonwspan => profile_rowspan} #{profile.registrations.size}
- if profile.registrations.size == 0
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
%td{:rowspan => profile_rowspan}
- profile.registrations.each_with_index do |registration, reg_index|
- if reg_index != 0
!= "</tr><tr valign=\"top\">"
- reg_rowspan = [registration.signups.payments.size, registration.signups.size, 1].detect { |i| i > 0 }
%td{:rowspan => reg_rowspan, :class => "registration"} #{registration.id}
%td{:rowspan => reg_rowspan} #{registration.firstname} #{registration.lastname}
%td{:rowspan => reg_rowspan} #{registration.signups.size}
- if registration.signups.size == 0
%td{:rowspan => reg_rowspan}
%td{:rowspan => reg_rowspan}
%td{:rowspan => reg_rowspan}
%td{:rowspan => reg_rowspan}
%td{:rowspan => reg_rowspan}
%td{:rowspan => reg_rowspan}
%td{:rowspan => reg_rowspan}
- registration.signups.each_with_index do |signup, signup_index|
- if signup_index != 0
!= "</tr><tr valign=\"top\">"
- signup_rowspan = [signup.payments.size, 1].detect { |i| i > 0 }
%td{:rowspan => signup_rowspan, :class => "signup"} #{signup.event.id}
%td{:rowspan => signup_rowspan} #{signup.event.event_title}
%td{:rowspan => signup_rowspan} #{signup.event.event_start}
%td{:rowspan => signup_rowspan} #{signup.payments.size}
- if signup.payments.size == 0
%td{:rowspan => signup_rowspan}
%td{:rowspan => signup_rowspan}
%td{:rowspan => signup_rowspan}
- signup.payments.each_with_index do |payment, payment_index|
- if payment_index != 0
!= "</tr><tr valign=\"top\">"
%td{:class => "payment"} #{payment.id}
%td #{payment.total}
%td #{payment.payment_date}
profile.rb
class Profile
has n, :registrations
end
registration.rb
class Registration
belongs_to :profile
has n, :signups
has n, :payments
end
signup.rb
class Signup
belongs_to :registration
belongs_to :event
has n, :payments
end
payment.rb
class Payment
belongs_to :registration
belongs_to :signup
end
event.rb
class Event
has n, :signups
has n, :payments, :through => :signups
end
If you are using MySQL, it can return results of a query as HTML. It'd be worth looking to see what it can do as a starting point.
Then, you could use Nokogiri to find empty cells and manipulate the HTML however you want.
精彩评论