Rails 4: Sorting by 2 different scopes/class methods - ruby-on-rails

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

Related

Calling a ActiveRecord class method for ActiveRecord_Relation as a receiver

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

Optional time where clause

I have an optional time where clause.
Namely where('created_at < ?', params[:infinite_scroll_time_buffer]).
This is included in a series of calls.
I realized that where could take a hash, and if the hash is empty, or is missing any attributes, they won't be included. This sounds great, as I could avoid checking if the params[:infinite_scroll_time_buffer] is there, and just include the where clause and let Rails take care of the rest.
The problem is the following:
def action
options = {}
options[:created_at] = params[:infinite_scroll_time_buffer]
Post.method.another_method.where(options).another_method
end
That would work, except the SQL Query checks that post.created_at = ? instead of post.created_at < ? (rightfully so).
I could have a range of times, but I can't for the life of me find a way for Time to reference the beginning of all time, or something like Time::THE_BEGINNING_OF_EVERYTHING_AS_WE_KNOW_IT_DUN_DUN_DUN
so that I could then have a range from that to the params[:infinite_scroll_time_buffer]. Is there another way to accomplish this?
Create a scope inside your post.rb:
scope :created_before, ->(time) { where('created_at < ?', time) }
Now:
def action
options = {}
Post.method.another_method.where(options).
created_before(params[:infinite_scroll_time_buffer]).
another_method
end
If you don't have the params[:infinite_scroll_time_buffer] present then don't make the query in the first place:
def action
options = {}
# more operation on options hash here
posts = Post.method.another_method.where(options)
posts = posts.created_before(params[:infinite_scroll_time_buffer]) if params[:infinite_scroll_time_buffer].present?
posts = posts.some_another_method
end

ActiveRecord where method datetime passed

Thanks for your continuing support in my latest venture in a Rails app. It's been a while since I've made something in Rails so here is my latest question. I appreciate the help you've all given in the past.
I have a Model called Event.rb which contains a date:date and time:time fields for the Event.
I also have a method inside of the model which is..
def begins
DateTime.new(date.year, date.month, date.day, time.hour, time.min, time.sec)
end
As I can't see if something has truly passed because I only have Date and Time separate so I need them together.
My question is...
I want to be able to add in the DateTime :begins into the following other method in my Model...
def self.past
where("date <= ?", TIME_NOW)
end
Just like I have a method which is...
def upcoming?
self.date >= Time.now
end
Which I could easily change self.date to begins and would past I would imagine?
Thanks!
Perhaps something like this will work for querying the database for past events using your existing date and time columns:
scope :past, lambda {
where("date <= ? and time <= ?",
Time.now.strftime("%Y-%d-%m"),
Time.now.strftime("%H:%M:%S")
)
}
past_events = Event.past
For checking the current instance, you could continue to use your begins method:
def past?
begins < DateTime.now
end
#event = Event.first
#event.past?

Count records created within the last 7 days

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)}

Rails 3 displaying tasks from partials

My Tasks belongs to different models but are always assigned to a company and/or a user. I am trying to narrow what gets displayed by grouping them by there due_at date without doing to many queries.
Have a application helper
def current_tasks
if user_signed_in? && !current_company.blank?
#tasks = Task.where("assigned_company = ? OR assigned_to = ?", current_company, current_user)
#current_tasks = #tasks
else
#current_tasks = nil
end
end
Then in my Main view I have
<%= render :partial => "common/tasks_show", :locals => { :tasks => current_tasks }%>
My problem is that in my task class I have what you see below. I have the same as a scope just named due_today. when I try current_tasks.due_today it works if I try current_tasks.select_due_today I get a undefined method "select_due_tomorrow" for #<ActiveRecord::Relation:0x66a7ee8>
def select_due_today
self.to_a.select{|task|task.due_at < Time.now.midnight || !task.due_at.blank?}
end
If you want to call current_tasks.select_due_today then it'll have to be a class method, something like this (translating your Ruby into SQL):
def self.select_due_today
select( 'due_at < ? OR due_at IS NOT NULL', Time.now.midnight )
end
Or, you could have pretty much the same thing as a scope - but put it in a lambda so that Time.now.midnight is called when you call the scope, not when you define it.
[edited to switch IS NULL to IS NOT NULL - this mirrors the Ruby in the question, but makes no sense because it will negate the left of the ORs meaning]

Resources