How can I alter the query below to only select records created within the last 7 days?
self.favorites.count
This function is located in my User model.
def calculate_user_score
unless self.new_record?
self.score = (self.links.count * 5) + (self.favorites.count * 0.5)
end
end
You can add a where-condition like this:
self.favorites.where('created_at >= ?', 1.week.ago).count
And for your calculate_user_score method, you probably want to do that for links as well:
def calculate_user_score
unless new_record?
self.score = (links.where('created_at >= ?', 1.week.ago).count * 5) +
(favorites.where('created_at >= ?', 1.week.ago).count * 0.5)
end
end
I recommend you add a scope to your model:
class User < ActiveRecord::Base
scope :recents, where("created_at > ?", Time.now-7.days)
end
Then you can do
self.favorites.recents.count
In Rails 4+
This code seems not working:
"created_at > ?", Time.now-7.days
I tried like:
scope :recent, -> { where("DATE(created_at) > ?", (Date.today).to_time - 7.days) }
self.links.where("created_at > ?", Time.now-7.days).count
If you're working in Rails, you can just use the ago datetime methods, instead of doing weird time math.
scope :recent, -> { where("created_at > ?", 1.week.ago) }
In Rails, you can usually avoid a lot of the complicated data preparation and type-casting you might have to do in other languages / frameworks.
Re: the original post, I would probably refactor it like this:
# Using association extensions here to filter this down,
# the ellipses parenthetical should be whatever you're using for your
# association definition.
has_many :links ( ... ) do
def since(last_date)
where('created_at > ?', last_date)
end
end
has_many :favorites (...) do
def since(last_date)
where('created_at > ?', last_date)
end
end
# Don't use magic numbers; codify them for context.
LINK_SCORE_MULTIPLIER = 5
FAVE_SCORE_MULTIPLIER = 0.5
# Note this does not persist it in the database; if you want it to persist
# you'll want to execute an update instead. However it does memoize it so multiple
# calls will pull from the in-memory cache of the object instead of re-querying it
def score(recalculate: true)
#score ||= (links.since(1.week.ago).count * LINK_SCORE_MULTIPLIER) +
(favorites.since(1.week.ago).count * FAVE_SCORE_MULTIPLIER)
end
Then you just reference it passively:
#user.score # runs the query and saves to memory
#user.score # pulls from memory
#user.score(recalculate: true) # re-runs the query and saves to memory
#user.save # persists the result (assuming you have a field for :score)
It might require refactoring, but depending on how your data is modeled, you might be able to use a counter_cache to track it (this would require a has_many,through association, and the counter_cache would be on the joining model.
I was looking for records which could return last 7 days i.e. not including today. But this worked for me and it can work for last n days.
last_n_days = 7
Model.where('created_at BETWEEN ? AND ?', Date.today-last_n_days, Date.today-1).count
with scope
scope :last_n_days, lambda {|n| where('created_at BETWEEN ? AND ?', Date.today - n, Date.today - 1)}
Related
I'm trying to sort my events by future events then by events that have already ended. I tried using 2 different scopes but I'm thinking now that I might need to use class methods instead. I'm just struggling on how, syntactically, I need to write these methods.
event.rb:
def active
self.event_date > Time.now
end
def inactive
self.event_date < Time.now
end
"event_date" is a datetime column in the events table.
events_controller.rb:
def index
#events = Event.all.sort_by {|a,b| [a.active, b.inactive]}
end
With this code, I'm getting an error: "undefined method `inactive' for nil:NilClass" but I've tried it several different ways and can't seem to figure out how to write it.
Your methods will be executed after running through the database and will be slow.
This can probably be improved:
Model
scope :active, -> { where('event_date >= ?', Time.now) }
scope :inactive, -> { where('event_date < ?', Time.now) }
Controller
#active_events = Event.active
#inactive_events = Event.inactive
#events = #active_events + #inactive_events
I want to create a class method for a class inherits ActiveRecord:Base.
What the method need to do is add where clauses based on the options and it works well.
class Article < ActiveRecord::Base
def self.list_by_params(params={})
articles = self
articles = articles.where(author_id: params[:author_id]) unless params[:author_id].blank?
articles = articles.where(category_id: params[:category_id]) unless params[:category_id].blank?
articles = articles.where("created_at > ?", params[:created_at].to_date) unless params[:created_at].blank?
articles
end
end
This code works fine in case of the call such as:
articles = Article.list_by_params({author_id: 700})
#=> Works fine as I expected.
articles = Article.joins(:authors).list_by_params({author_id: 700})
#=> Works fine as I expected.
However, the problem is that, if I want to call the list_by_params without filtering params, then it lose its former relations. For example:
articles = Article.joins(:authors).list_by_params({})
#=> articles is just a `Article` (not an ActiveRecord_Relation) class itself without joining :authors.
Is there any chance that I made a mistake?
Thanks in advance.
What you are looking for is a scope.
I would do something like this
scope :for_author, lambda { |author| where(author_id: author) unless author.blank? }
scope :in_category, lambda { |category| where(category_id: category) unless category.blank? }
scope :created_after, lambda { |date| where('created_at > ?', date.to_date) unless date.blank? }
scope :list_by_params, lambda do |params|
for_author(params[:author_id])
.in_category(params[:category_id])
.created_after(params[:created_at])
end
Now you can reuse the components of your query. Everything has a names and it gets easier to read the code.
For the self explanation, I've solved the problems by using where(nil).
Actually, Model.scoped returned anonymous scope but the method has been deprecated since Rails version 4. Now, where(nil) can replace the functionality.
class Article < ActiveRecord::Base
def self.list_by_params(params={})
articles = where(nil) # <-- HERE IS THE PART THAT I CHANGED.
articles = articles.where(author_id: params[:author_id]) unless params[:author_id].blank?
articles = articles.where(category_id: params[:category_id]) unless params[:category_id].blank?
articles = articles.where("created_at > ?", params[:created_at].to_date) unless params[:created_at].blank?
articles
end
end
I have this class method:
def self.default_column
"created_at"
end
How can I rewrite the following function, so that I can make use of my default_column method?
def next
User.where("created_at > ?", created_at).order('created_at ASC').first
end
I tried things like these...
def next
User.where("#{default_column} > ?", default_column).order('#{default_column} ASC').first
end
... but I must be awfully wrong here because it doesn't work at all.
Thanks for any help.
You can use:
def next
User.where("#{User.default_column} > ?", self.send(User.default_column)).order("#{User.default_column} ASC").first
end
Or even better
def next
klass = self.class # This is supposing you are inside User model
# Otherwise just use klass = User
klass.where("#{klass.default_column} > ?", self.send(klass.default_column))
.order(klass.arel_table[klass.default_column].asc)
end
Notice that if you handle the method in this way, you cannot chain it: like User.where(name: 'something').next
If you want to achieve this, you have to move next to be def self.next and in that case, you have to pass an instance of the user to it, like this:
def self.next(user)
klass = user.class
klass.where("#{klass.default_column} > ?", user.send(klass.default_column))
.order(klass.arel_table[klass.default_column].asc)
end
In this way you can write something like: User.where(name: 'test').next(#user). You can optionally chain .first to get directly the result, but in this way you will not be able to chain other things, like User.where(name: 'test').next(#user).where(email: 'my#mail.com')
Finally, if you want pure AREL (for portability)
def self.next(user)
klass = user.class
arel = klass.arel_table
column = klass.default_column # This helps cleaning up code
column_value = user.send(column)
klass.where(arel[column].gt(column_value))
.order(arel[column].asc)
end
def next
default_column = self.class.default_column
User
.where("#{default_column} > ?", send(default_column))
.order("#{default_column} ASC")
.first
end
i've written the following sum/group methods in my 'StatementSales' model and want to be able to constrain the results by date, at the moment it's just producing totals for all valid db entries. In my views I want to provide links to 'One Week, One Month, Three Months, One Year' etc and ideally pass these to the methods below. How should I approach this?
def self.total_units
sum(:units)
end
def self.units_by_store
group(:store).sum(:units)
end
def self.units_by_territory
group(:territory).sum(:units)
end
def self.units_by_upc
group(:upc).sum(:units)
end
Many thanks in advance!
You could use scopes
Add this to your class
scope :between_dates, lambda { |start_date, end_date| where("date < #{end_date} AND date >= #{start_date}") }
scope :one_week, between_dates(Date.today, Date.today + 7.days)
Then you can do
def self.total_units
self.one_week.sum(:units)
end
So I have some posts, and would like to show n..m most recent entries in the sidebar (these numbers being set in a config)
I can get the latest n records easily enough
class Post < ActiveRecord::Base
default_scope :order => "created_at DESC"
scope :published, lambda { where("blog_entries.created_at <= ?", Time.zone.now) }
scope :latest, lambda { |n| published.limit(n) }
end
#posts = Post.latest(6)
But what I'd like is
#posts = Post.published.limit(6, 12)
but this gives wrong number of arguments, so is there any way in AR? Right now I'm playing with will_paginate, but it seems hacky to use it for this.
Ok, so the answer is, I think:
#posts = Post.published.limit(6).offset(5)
It will retrieve 6 posts, starting from the sixth.
edit2: About the limit([6, 12]), I find that strange:
attr_accessor :limit_value
def limit(value)
relation = clone
relation.limit_value = value
relation
end
def build_arel
...
arel.take(connection.sanitize_limit(#limit_value)) if #limit_value
...
end
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
elsif limit.to_s =~ /,/
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
else
Integer(limit)
end
end
So I don't really see how it works with an array. But I obviously missed something. Do you see what?
For rails 5 (not sure for rails 4). offset(x).limit(y) works correctly. limit(y).offset(x) still behaves as described in other answers.