Organizing and mapping records in Ruby on Rails

I have the following table of Sales from some Articles

id | user_id | article_id | status
1  | 1       | 1          | 0
2  | 1       | 2          | 0
3  | 2       | 2          | 0
4  | 1       | 1          | 1
5  | 2       | 2          | 1

Where status = 0 is unconfirmed and status = 1 is confirmed

The logic follows: The user clicks on a button to buy an Article Then, a record is saved on the table with the unconfirmed status. After the payment is confirmed, another record is save on the table with the status confirmed. Both records are saved on the table to keep a timeline of the whole transaction. The id field determines the oldest record from a given transaction.

In my application, I wanted to get a list of 5 Most Sold Articles, here's how I've done it so far:

# In articles_helper.rb

  def get_articles_sold
    # Fetch all Articles that are present in the Sales table
    sales = Article.where(id: Sale.select(:article_id).map(&:article_id))

    sold = []
    sales.each do |s|
      s.status.group_by(&:status).each do |status, sale|
        if status.to_sym == :confirmed
          sold << {article: s, sold: sale.count}
        end
      end
    end

    # Returns the sold array
    sold
  end

# In the Article model there's this Virtual Attribute 'status'
# It returns me the last status from a given transaction

def status
    sales = Sale.where(article_id: self.id).order(id: :desc)

    statuses = []

    sales.group_by(&:user_id).each do |user, sale|
      statuses << sale.first
    end

    statuses
  end

#In my application

@most_sold_articles = get_articles_sold.sort_by{|v| v[:sold]}.reverse

It returns me something like:

@most_sold_articles = [
   {article: #<some_article_entity>, sold: 4},
   {article: #<some_article_entity>, sold: 2},
   {article: #<some_article_entity>, sold: 1},
]

This code actually works and give me what I want, but I think it's a lot of code for a Ruby application. Is there any ellegant or easier way of getting this same result?

Upvotes: 1

Views: 154

Answers (1)

Deepak Mahakale
Deepak Mahakale

Reputation: 23671

I think this should do it

@articles = Article
  .joins(:sales)
  .select("articles.*, count(sales.article_id) as sold")
  .group('articles.id').order('sold DESC').limit(5)

If you want the same format as in your answer

@most_sold_articles = @articles.map{|a| {article: a, sold: a.sold}}

#=> [
#=>   {article: #<some_article_entity>, sold: 100},
#=>   {article: #<some_article_entity>, sold: 80},
#=>   {article: #<some_article_entity>, sold: 75},
#=>   {article: #<some_article_entity>, sold: 66},
#=>   {article: #<some_article_entity>, sold: 30}
#=> ]   

Upvotes: 3

Related Questions