5
\$\begingroup\$

My code works perfectly, however, I know my approach isn't best practice practice. I would appreciate the effort of anyone who tries to explain in details on how I could refactor this code using either(presenters, decorators or poros) - I have no deep understanding of either yet. Here is the code below:

In my events controller, I have a method called tickets_report defined as such

def tickets_report 
  @all_bookings = @event.bookings
  @ticket_types = @event.ticket_types
  @attendees = @event.attendees 
end

then, I use the @ticket_types instance variable in my views to plot a table as follows:

<tbody>

<% grand_quantity = ticket_left = quantity_sold = total_price = total_amount = 0 %>
<% @tickets.each_with_index do |ticket, index| %> 
    <% remains = ticket.quantity.to_i - ticket.user_tickets.count.to_i 
       amount = ticket.price.to_f * ticket.user_tickets.count.to_i
       grand_quantity += ticket.quantity 
       ticket_left += remains 
       quantity_sold += ticket.user_tickets.count 
       total_price += ticket.price.to_f 
       total_amount += amount 
    %>
    <tr>
            <td><%= index + 1%></td>
            <td><%= ticket.name %></td>
            <td><%= ticket.quantity %></td>
            <td><%= remains %></td>
            <td><%= ticket.user_tickets.count %></td>
            <td><%= number_to_currency(ticket.price)%></td>
            <td><%= number_to_currency(amount)%></td>
          </tr>

<% end %> 
 <tr class="gtotal">  
    <td colspan="2">Grand Total</td>
    <td><%= grand_quantity %></td>
    <td><%= ticket_left %></td>
    <td><%= quantity_sold %></td>
    <td><%= number_to_currency(total_price)%></td>
    <td><%= number_to_currency(total_amount)%></td>
  </tr>
  </tbody>

My problem is this calculations done in the view is really not a good thing, and this is how far I have gone trying to fix this myself

module Events
  class Ticket
    def initialize(ticket)
      @ticket = ticket
    end 

    def name 
      @ticket.name 
    end 

    def quantity 
      @ticket.quantity 
    end 

    def price 
      @ticket.price.to_f 
    end 

    def user_count 
      @ticket.user_tickets.count.to_i 
    end 

    def remains 
      @ticket.quantity.to_i - user_count 
    end 

    def amount 
      price  * user_count 
    end 
  end 

  class TicketReportPresenter < Ticket 
    @@grand_quantity = @@ticket_left = @@quantity_sold = 0 
    @@total_price = @@total_amount = 0 

    def initialize(ticket)
      @ticket = Ticket.new(ticket)
      @@grand_quantity += @ticket.quantity.to_i  
      @@ticket_left += @ticket.remains 
      @@quantity_sold += @ticket.user_count 
      @@total_price += @ticket.price 
      @@total_amount ++ @ticket.amount 
    end 

    class << self 

      def grand_quantity 
        @@grand_quantity
      end 

      def ticket_left 
        @@ticket_left 
      end 

      def quantity_sold 
        @@quantity_sold
      end 

      def total_price 
        @@total_price
      end 

      def total_amount 
        @@total_amount 
      end 
    end    
  end
end

The new tickets_report method in my controller

def tickets_report 
  @all_bookings = @event.bookings
  @ticket_types = Events::TicketReportPresenter.new(@event.ticket_types.first)
  @attendees = @event.attendees 
end

However, things don't seem to be going right at this point. For instance I cannot call ActiveRecord relationships on the tickets object I defined. What is the best approach to refactor the logic present in the view, a detailed description and source code will suffice plus links to other resources.

\$\endgroup\$
1
\$\begingroup\$

I finally figured out a better approach to avoid those complex logic in my view by using a presenter pattern. I create a presenter directory in my app folder and subfolder for the event controller and a tickets_report_presenter.rb file with the following code

module Events
  class TicketsReportPresenter
    attr_reader :event 

    def initialize(event)
      @event = event
    end

    def all_bookings 
      event.bookings.paginate(page: 1, per_page: 20)
    end 

    def attendees 
      event.attendees.paginate(page: 1, per_page: 20)
    end 

    def ticket_types 
      event.ticket_types.map do |ticket| 
         Report::Ticket.new(ticket) 
      end
    end 

    def grand_quantity 
      ticket_types.map(&:quantity).inject(0, &:+)
    end

    def ticket_left 
      ticket_types.map(&:remains).inject(0, &:+)
    end

    def quantity_sold 
      ticket_types.map(&:tickets_sold).inject(0, &:+)
    end 

    def total_price 
      ticket_types.map(&:price).inject(0, &:+)
    end

    def total_amount 
      ticket_types.map(&:amount).inject(0, &:+)
    end   

  end

  module Report 
    class Ticket
      attr_reader :ticket

      def initialize(ticket)
        @ticket = ticket
      end  

      def name
        ticket.name  
      end 

      def quantity 
        ticket.quantity.to_i 
      end 

      def tickets_sold
        ticket.user_tickets.count.to_i 
      end 

      def price 
        ticket.price.to_i 
      end

      def remains 
        quantity - tickets_sold 
      end 

      def amount 
        price * tickets_sold 
      end 
    end 
  end  
end 

Then in my tickets_report action, I did this

def tickets_report 
    @presenter = Events::TicketsReportPresenter.new(@event)
end 

This allows be to call the following methods in my view:

@presenter.all_bookings 
@presenter.attendees 
@presenter.ticket_types
@presenter.grand_quantity 
@presenter.ticket_left 

Thanks to anyone who viewed this.

\$\endgroup\$

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.