Reputation: 26061
I have two ruby scope but they are not completely using activecord. How can I change it to use only activerecord or sql?
class Article < Comfy::Cms::Page
has_many :view_mappings
has_many :favorite_mappings
scope :top_by_number_of_favorites, -> { article.sort_by(&:favorite_score).take(10) }
scope :top_by_number_of_views, -> { article.sort_by(&:view_score).take(10) }
def favorite_score
favorite_mappings.since(2.weeks.ago).count * 5
end
def view_score
view_mappings.since(2.weeks.ago).count
end
create_table "view_mappings", force: :cascade do |t|
t.integer "article_id"
t.date "date_viewed"
t.string "device_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "favorite_mappings", force: :cascade do |t|
t.integer "article_id"
t.string "device_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.date "date_liked"
end
Upvotes: 2
Views: 80
Reputation: 1989
Try this:
class Article < Comfy::Cms::Page
has_many :favorite_mappings
has_many :view_mappings
def self.top_by_number_of_favorites(since = 2.weeks.ago, amount = 10)
with_favorite_score
.where('date_liked >= ?', since)
.order('favorite_score DESC')
.limit(amount)
end
def self.top_by_number_of_views(since = 2.weeks.ago, amount = 10)
with_view_score
.where('date_viewed >= ?', since)
.order('view_score DESC')
.limit(amount)
end
def self.with_favorite_score
joins(:favorite_mappings)
.select('articles.*, COUNT(favorite_mappings.id) AS favorite_score')
.group(:id)
end
def self.with_view_score
joins(:view_mappings)
.select('articles.*, COUNT(view_mappings.id) AS view_score')
.group(:id)
end
end
I also made it so you can change the lookback date, so instead of 2 weeks, you could get the top articles from the last month, year, or since any arbitrary date. The limit is now changeable as well, so you can get the top 20, 50, 100, etc. You can also access the favorite_score/view_score like this @articles.top_by_number_of_views.first.view_score
.
Upvotes: 2
Reputation: 6545
You should use some SQL magic:
scope :top_by_number_of_favorites, -> {
joins(:favorite_mappings).
where("date_liked >= ?", 2.weeks.ago).
group(:id).
select("comfy_cms_pages.*, COUNT(*) AS favorite_score").
order("favorite_score DESC").
limit(10)
}
First two lines – we join favorite_mappings to query result, selecting only ones which are 2 weeks old or newer. Then we group results by arcticle IDs, specifying that we want to select COUNT of likes. And then we just order them top to bottom, and taking first 10 records.
More on scopes:
http://guides.rubyonrails.org/active_record_querying.html#scopes
More on aggregate functions:
https://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html http://www.postgresql.org/docs/9.1/static/functions-aggregate.html
Please note that you will have article['favorite_score'] for score. Also, all other fields of Article should be available to your code, as we specified *
in list of fields to select.
Upvotes: 1