1
\$\begingroup\$

TL;DR

I need a way to refactor a complex user dashboard with several objects and some complex data to display a accounting chart. I have read about both presenters (also called decorators or view models) and service object, but I'm not sure if I should use one or the other, or both? And how to implement this? My model and controller are pretty messy, so if anyone have some hints or suggestions, would I really appreciate it.

Long version:

I'm currently working on an application that helps users to coordinate dinner clubs and all related accounting. (A dinner club is where people in a group, take turns to cook for the rest and then you pay a small amount to participate. This is pretty normal in dorms and colleges where I'm from). When you login are you presented to a dashboard with all important information separated in three blocks: the next dinner and option to registrate, the next dinner where you have to cook, and accounting overview like current debt, spendings etc.

This gets pretty messy: a lot of instance variables in my controller, and a lot of methods to present this view in my models.

So now to the real question: can anyone tell me any good hints, design patterns or general advice to help me refactor this code? I have read about presenters, service objects, decorators etc. but I'm not sure which to use and how.

Here are some examples of how bad it looks right now (a kitchen is the group of people that have dinner together):

# app/controllers/dashboard_controller.rb
def index
  @user = current_user
  @kitchen = @user.kitchen

  @upcoming_dinner_clubs = @user.upcoming_dinner_clubs # The next dinner clubs where the current user have to cook
  @users_next_dinner_club = @user.next_dinner_club # The first of upcoming_dinner_clubs
  @unpriced_dinner_clubs = @user.unpriced_dinner_clubs # Old dinner clubs where the user haven't specified a price yet

  # The next dinner club in the kitchen
  @next_dinner_club = @kitchen.next_dinner_club if @kitchen.next_dinner_club
  @todays_dinner_club = @next_dinner_club if @next_dinner_club && @next_dinner_club.date.today?
end

This view shows some chart of the expenses and spendings of a user, rendered through JavaScript. My views are in HAML.

# app/views/dashboard/_expenses.html.haml
%h2 Dit forbrug
  %p
    = t '.usage_html', expenses: number_to_currency(@user.last_month_expenses), spendings: number_to_currency(@user.last_month_spendings), results: number_to_currency(@user.last_month_results)
    = content_tag :div, "", id: "revenue_chart", class: "chart dashboard-chart", data: { chart: @user.usage_chart_data }
    = t '.results_html', results: number_to_currency(@user.total_results)
    = content_tag :div, "", id: "result_chart", class: "chart dashboard-chart", data: { chart: @user.result_chart_data }

Don't want you to bore you with all the details, and how the methods work, but this is the methods i have, only for displaying the expenses and spendings data in the view:

# app/models/user.rb
def last_month_expenses
  expenses_for((1.month + 1.day).ago, 1.day.ago)
end

def last_month_spendings
  spendings_for((1.month + 1.day).ago, 1.day.ago)
end

def last_month_results
  results_for((1.month + 1.day).ago, 1.day.ago)
end

def spendings_for(start_date, end_date, kitchen)
end

def expenses_for(start_date, end_date, kitchen)
end

def fee_for(start_date, end_date, kitchen)
end

def accounting_query_conditions(start_date, end_date, kitchen)
  {date: start_date..end_date, kitchen_id: kitchen.id}
end

def results_for(start_date, end_date)
  spendings_for(start_date, end_date) - expenses_for(start_date, end_date)
end

def total_fee(date = Date.today, kitchen = primary_kitchen)
end

def total_spendings(date = Date.today, kitchen = primary_kitchen)
end

def total_used_on_dinner_clubs(date = Date.today, kitchen = primary_kitchen)
end

def total_expenses(date = Date.today, kitchen = primary_kitchen)
end

def total_results(date = Date.today, kitchen = primary_kitchen)
  total_expenses(date, kitchen) - total_spendings(date, kitchen)
end
\$\endgroup\$
2
\$\begingroup\$

There is a lot here and without diving into the pattern based approach, my first question would be have you considered using rails scopes? Maybe that answer is overly simplistic, but using scopes would get rid of a lot of your reporting methods and make you be able to do something like:

Spendings.last_month
Results.last_month

And speaking of Spendings and Results, it sounds like you may want to create a rails class or model for some of these items (like Results and Spendings and Expenses) instead of just querying them from a dashboard view? I have some tableless models in my apps that are used for reporting purposes. It seems like a much cleaner approach than having a long dashboard controller doing the querying for you. In that same vein, another data / reporting centric approach that isn't rails specific is to create reporting tables (these could be rails models) that are divorced from the transactional data just so you aren't querying reports from the live system which can lead to performance hits for both users on the transactional side and the reporting side.

Rails is very "flat" from a traditional 3rd normal form database architecture, so it isn't much work to create reporting tables.... Just some thoughts. Good luck.

\$\endgroup\$
  • \$\begingroup\$ Thank you for your answer! I have currently added some widgets classes (im using apotomo,but it's like presenters) to clean up my controllers, and encapsulate the logic there. The next thing i need to refactor is then the models. I will definitely try your suggestion, it seems like a very good solution :) \$\endgroup\$ – jokklan Sep 18 '13 at 9:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.