I've to make posts inactive after a given date and time by the user who creates these posts. I've added a boolean field "published" to make a post active or unactive.
If the post is active, every visitor will see the post. If it's inactive, the post will be seen in the user dashboard as inactive.
For you guys what is the best way to make a post inactive after the given datetime passed? That should be automatic but I don't think the suitable way is using Active Job and ;
Check all posts in the db if active,
If active, compare the given datetime and current datetime
If needed, make it inactive
I'm using heroku, so that'll make my worker busy all the time.
If you simply plan to fetch and display posts, rather than setting the boolean value, you could just add a scope to the model:
scope :published, -> { where('publish_at < ?', Time.now) }
And use like this Post.published
Let me know if this helps.
Update 1:
If you need to determine if a post is active or not, add a method to the model like this,
def active?
self.publish_at < Time.now
end
You can do some things:
Option 1:
Based in the #jawad-khawaja answer, I think a better approach is to use expired_at instead of published_at because for me, a publish date is a date when a post is available for people. This way the user can define an expiration date for his own post (not the same publish date).
Here some example how to you can get active/inactive posts based on its expiration date
# active posts
scope :published, -> { where('expired_at > ?', Time.now) }
# inactive posts
scope :unpublished, -> { where('expired_at < ?', Time.now) }
Option 2:
If you have an attribute published as boolean and you really need mark it automatically, the option is to use whenever gem
CONS for this option:
You will have two attributes representing the same thing: published as boolean and expiration_datetime as datetime
If the minimum unit for an expiration date is a minute, you need to check every minute if every not-expired post enter to the expired state. And you will have probably an overload for your server.
Conclusion:
My recommended way is to choose the option 1.
Related
In my app there is a financial overview page with quite a lot of queries. This page is refreshed once a month after executing a background job, so I added caching:
#iterated_hours = Rails.cache.fetch("productivity_data", expires_in: 24.hours) do
FinancialsIterator.new.create_productivity_iterations(#company)
end
The cache must expire when the background job finishes, so I created a model CacheExpiration:
class CacheExpiration < ApplicationRecord
validates :cache_key, :expires_in, presence: true
end
So in the background job a record is created:
CacheExpiration.create(cache_key: "productivity_data", expires_in: DateTime.now)
And the Rails.cache.fetch is updated to:
expires_in = get_cache_key_expiration("productivity_data")
#iterated_hours = Rails.cache.fetch("productivity_data", expires_in: expires_in) do
FinancialsIterator.new.create_productivity_iterations(#company)
end
private def get_cache_key_expiration(cache_key)
cache_expiration = CacheExpiration.find_by_cache_key(cache_key)
if cache_expiration.present?
cache_expiration.expires_in
else
24.hours
end
end
So now the expiration is set to a DateTime, is this correct or should it be a number of seconds? Is this the correct approach to make sure the cache is expired only once when the background job finishes?
Explicitly setting an expires_in value is very limiting and error prone IMO. You will not be able to change the value once a cache value has been created (well you can clear the cache manually) and if ever you want to change the background job to run more/less often, you also have to remember to update the expires_in value. Additionally, the time when the background job is finished might be different from the time the first request to the view is made. As a worst case, the request is made a minute before the background job updates the information for the view. Your users will have to wait a whole day to get current information.
A more flexible approach is to rely on updated_at or in their absence created_at fields of ActiveRecord models.
For that, you can either rely on the CacheExpiration model you already created (it might already have the appropriate fields) or use the last of the "huge number of records" you create. Simply order them and take the last SomeArModel.order(created_at: :desc).first
The benefit of this approach is that whenever the AR model you create is updated/created, you cache is busted and a new one will be created. There is no longer a coupling between the time a user called the end point and the time the background job ran. In case a record is created by any means other than the background job, it will also simply be handled.
ActiveRecord models are first class citizens when it comes to caching. You can simply pass them in as cache keys. Your code would then change to:
Rails.cache.fetch(CacheExpiration.find_by_cache_key("productivity_data")) do
FinancialsIterator.new.create_productivity_iterations(#company)
end
But if at all possible, try to find an alternative model so you no longer have to maintain CacheExpiration.
Rails also has a guide on that topic
I tried to get this answered with no luck so I'll try again.
I've implemented the railcast timezone goodies to allow the user to set their time zone. It works. Time.zone.now gives the correct time zone. It's in here
http://stevenyue.com/2013/03/23/date-time-datetime-in-ruby-and-rails/
I have events and have been trying to get the datetime_select in my form to give me a time that is also in the user's time zone.
My goal is to be able to compare it to current time (Time.zone.now) to validate that the start time is not before current time. And eventually end time > start time etc.
I've tired several ways including this one with no luck...
This one - he answered his own question later (is exactly my problem)
Rails datetime_select posting my current time in UTC
def start_date_cannot_be_in_the_past
if date_start.in_time_zone(Time.zone) < Time.zone.now
errors.add(:date_start, "has already passed")
end
end
The above doesn't seem to work because you can't extract date_start just like that. It's separated into different components, so I tried to do something like this
def start_date_cannot_be_in_the_past
date = DateTime.new(params[event][date_start(1i)].to_i, params[event][date_start(2i)].to_i, params[event][date_start(3i)].to_i, params[event][date_start(4i)].to_i, params[event][date_start(5i)].to_i)
if date.in_time_zone < Time.zone.now
errors.add(:date_start, "has already passed")
end
end
In my model I can't access params so I don't know how to get this to work...
I want to move to a jquery calendar/time picker if possible as I can't seem to get this work.. but any suggestions on alternatives or on this is appreciated..
Make sure you have this line in your model:
validate :start_date_cannot_be_in_the_past
and then this method can be something like:
private
def start_date_cannot_be_in_the_past
errors.add(:date_start, "has already passed") if self.date_start < Time.zone.now
end
See this for more information.
I'm developing a Rails4 app and I have lots of date attributes in a model. For example;
first_payment_date
last_payment_date
first_application_date
last_application_date
first_result_validation_date
last_result_validation_date
etc.
I want to automate things a little bit and want my application act with these dates. For example, user's won't be able to do payment, after the last_payment_date, or they will not be able to make an application before first_application_date.
What is the best approach to plan this kind of thing? I heard about "state machines" and state_machine GEM but I'm not sure if it's the right thing for me.
Thanks.
If these dates are in the User model, you can create several helper methods inside it to return true of false based on your conditions:
def can_deliver_payment?
self.last_payment_date > Date.today
end
def can_make_application?
self.first_application_date > Date.today
end
# etc
So now when you have an instance of User, you can check these conditions on a more readable way.
I'm developing an event calendar, where each hour is a slot that allows users to sign one appointment.
Since there is a feature that allows users to update the slot (and also change date/time) I'd like to perform some check before to update the value, making sure that the slot is free.
So I ended up with a method like this:
if Event.find(:all, :conditions => ["start_at = ? AND event_calendar_id = ?", self.start_at, self.id])
errors.add :base, "You cannot save this appointment in that slot."
return false
end
By the way it creates issues when updating other fields without changing the datetime field, because it finds itself and raises the exception making impossible to update it.
Is there a way I can access database data such as the current id so I can filter out itself from the values, or check if the submitted datetime field is equal to the database one (so i can skip this check).
What's the best way to do this?
Thanks.
P.S. I'm using rails 3.2.3
To exclude self from the results just add a condition to your where excluding its id. Also save some memory and processor time by calling exists?, which just returns true or false instead of fetching an entire row and building a new object:
Event.where( :start_at => start_at,
:event_calendar_id => event_calendar.id ).
where( "id <> ?", id ).
exists?
I have a Day model which has a date column. I have to implement the validation that the date column must not have a past date. If the date is a past date it must not get saved to the database and give the appropriate error message right on the. I know I can put this validation in the controller, but I think it violates the MVC rules i.e keeping the business logic away from the controllers.
Is there any way to put this validation in the model or anywhere else and if the date is a past date then it must redirect back to the new action with the message "Date cannot be a past date"
Please Help
Thanks in advance
In your model you need add a validation
validate :not_past_date
def not_past_date
if self.date < Date.today
errors.add(:date, 'not in past')
end
end
After in your controller, you just check if save return true or false. False is send when you don't validate your model. If false redirect to another controller.
Edit :
Like said by Simone Carletti in comment you can use #past?
validate :not_past_date
def not_past_date
if self.date.past?
errors.add(:date, 'not in past')
end
end
I know I can put this validation in
the controller, but I think it
violates the MVC rules i.e keeping the
business logic away from the
controllers.
Eeh, in Rails you should put validations in the model (not in the controller!). See:
http://guides.rubyonrails.org/active_record_validations_callbacks.html.
#Simone and #shingara using the #past? method will compare a date with the current DateTime in UTC. So you may face a problem in two occasions here:
If you want to get all elements with a DateTime set to a day, wether they began at the beginning of the day or not;
If you are working on another TimeZone.
There are workarounds to deal with it, but it's just more straightforward to do the first #shingara way: self.date < Date.today