rails3 user can only vote once per day - ruby-on-rails

class Rate < ActiveRecord::Base
attr_accessible :image_id, :rate, :user_id
belongs_to :image
belongs_to :user
validate :user_can_rate_after_one_day
before_save :default_values
def default_values
self.rate ||=0
end
protected
def user_can_rate_after_one_day
r=Rate.where(:image_id =>image_id, :user_id=> user_id).order("created_at DESC").limit(1)
if( (Time.now - 1.day) < r[0].created_at)
self.errors.add(:rate,"you can only vote once per day")
else
return
end
end
end
I have one rate model, and i want the user can only rate once per day. i write the user_can_rate_after_one_day method to validte it. If i delete the function, the user can rate many time, if i add this function, user can not rate it. Anyone knows what's wrong here? Thanks

I have also the same type of problem. I tried to create an application in which a particular user can add standup for the current day only. I wrote the code in my controller create action. The logic is almost similar to your question, but I found one issue in your code due to which your date is not compared properly Time.now - 1.day return in this format 2018-01-22 11:24:23 +0545 and standup[0].created_at return in this format Mon, 22 Jan 2018 08:58:57 UTC +00:00.
So, to solve this issue, I converted both the date in same format i.e
(Time.now - 1.day).strftime('%F %H:%M:%S') < standup[0].created_at.strftime('%F %H:%M:%S') which returns true, and the code works.
It might be helpful to other viewers as well.

Related

Custom validator to prevent overlapping appointments in Rails 4 app?

I need help writing a custom validation to prevent overlapping appointments in a Rails 4 app. I'm coding this app to teach myself Ruby & Rails. While researching the issue, I discovered a gem called ValidatesOverlap, but I want to write my own validator for learning purposes.
My Appointment model has an "appointment_at" column of the datetime datatype and a "duration" column of the time datatype. The Appointment model has a "has_many :through" association with Member and Trainer models. Appointments:
belongs_to :member
belongs_to :trainer
Existing validations in the Appointment model include:
validates :member, uniqueness: {scope: :appointment_at, message: "is booked already"}
validates :trainer, uniqueness: {scope: :appointment_at, message: "is booked already"}
The custom validator needs to prevent members or trainers from scheduling overlapping appointments. Right now, I can prevent "duplicate appointments" from being saved to the database, but can't stop "overlapping" ones. For example, if trainer_1 is booked for a 1 hour appointment with member_1 which starts at 7:00 am, my model validations prevent member_2 from booking an appointment with trainer_1 for 7:00 am. However, I have no current means of preventing member_2 from scheduling a session with trainer_1 for 7:01 am! I'm working with two attributes: "appointment_at," which is the start time and "duration" which is the total time of an appointment. I'd prefer to keep those attributes/columns if I can easily calculate "end time" from the "appointment_at" and "duration" values. I haven't figured out how to do that yet :)
I'd appreciate any thoughts or suggestions about how I can approach solving the problem of overlapping appointments (without using a gem). Many thanks in advance!
I had the same problem a while ago. You need a scope :overlapping, that reads overlapping appointments for an appointment and a validator to check. This example is for a PostgreSQL DB. You have to adjust it for your DB, if you're using another DB.
class Appointment < ActiveRecord::Base
belongs_to :member
belongs_to :trainer
validate :overlapping_appointments
scope :overlapping, ->(a) {
where(%q{ (appointment_at, (appointment_at + duration)) OVERLAPS (?, ?) }, a.appointment_at, a.appointment_to)
.where(%q{ id != ? }, a.id)
.where(trainer_id: a.trainer.id)
}
def find_overlapping
self.class.overlapping(self)
end
def overlapping?
self.class.overlapping(self).count > 0
end
def appointment_to
(appointment_at + duration.hour.hours + duration.min.minutes + duration.sec.seconds).to_datetime
end
protected
def overlapping_appointments
if overlapping?
errors[:base] << "This appointment overlaps with another one."
end
end
end
Give it a try and let me know, if it helped you.

Rails validation / scope on 24 hour period

I'm trying to put a validation on a record. The validation will check that the record can't be created if the ip_address and post_id are the same. This works good.
I am trying to add another condition that will allow this duplication only if it is after a 24 hour period, if so, allow it to save, but all future saves will be disabled again until the 24 period is over.
Here has been my best attempt so far:
validates_uniqueness_of :ip_address, scope: :impressionable_id,
conditions: -> {where('created_at < ?', Time.now - 24.hours)}
So the validation should somehow only check the "latest" record of the group it finds to do the validation to be accurate
Thank you!
It might be easier to just make an explicit validator method for this:
class Foo < ActiveRecord::Base
validate :ip_address_uniqueness, on: :create
# ...
def ip_address_uniqueness
existing = Foo.where(impressionable_id: self.impressionable_id)
.where(ip_address: self.ip_address)
.where(created_at: Time.current.all_day)
errors.add(:ip_address, 'cannot be used again today') if existing
end
end

Rails replace collection instead of adding to it from a has_many nested attributes form

I have these models (simplified for readability):
class Place < ActiveRecord::Base
has_many :business_hours, dependent: :destroy
accepts_nested_attributes_for :business_hours
end
class BusinessHour < ActiveRecord::Base
belongs_to :place
end
And this controller:
class Admin::PlacesController < Admin::BaseController
def update
#place = Place.find(params[:id])
if #place.update_attributes(place_params)
# Redirect to OK page
else
# Show errors
end
end
private
def place_params
params.require(:place)
.permit(
business_hours_attributes: [:day_of_week, :opening_time, :closing_time]
)
end
end
I have a somewhat dynamic form which is rendered through javascript where the user can add new opening hours. When submitting these opening hours I would like to always replace the old ones (if they exist). Currently if I send the values via params (e.g.):
place[business_hours][0][day_of_week]: 1
place[business_hours][0][opening_time]: 10:00 am
place[business_hours][0][closing_time]: 5:00 pm
place[business_hours][1][day_of_week]: 2
place[business_hours][1][opening_time]: 10:00 am
place[business_hours][1][closing_time]: 5:00 pm
... and so forth
These new business hours get added to the existing ones. Is there a way to tell rails to always replace the business hours or do I manually have to empty the collection in the controller every time?
Bit optimizing the solution proposed #robertokl, to reduce the number of database queries:
def business_hours_attributes=(*args)
self.business_hours.clear
super(*args)
end
This is the best I could get:
def business_hours_attributes=(*attrs)
self.business_hours = []
super(*attrs)
end
Hopefully is not too late.
You miss id of business_hours try:
def place_params
params.require(:place)
.permit(
business_hours_attributes: [:id, :day_of_week, :opening_time, :closing_time]
)
end
That's why the form is adding a new record instead of updating it.

Whenever not running method okay

I need to make a XML request every minute, and then store the data in a Medida object. Every Meter object has many Medidas (measurements).
class Meter < ActiveRecord::Base
has_one :user_type
has_one :user
has_many :medidas
attr_accessible :user_type_id, :user_id, :address, :etc
def read_data
u = User.find(self.user_id)
Medida.get_data(self.address,u.user_key, self.id)
end
class << self
def all_read_data
meters = Meter.all
meters.each do |meter|
meter.read_data
end
end
end
end
Medidas.rb looks like
class Medida < ActiveRecord::Base
belongs_to :meter
attr_accessible :some_attr
class << self
def get_data(address,user_key, meter_id)
url="some_url_with#{variables}.xml?#{a_key}"
response = HTTParty.get(url)
hash = Hash.from_xml(response.to_s)
hash = Medida.some_funct(hash)
data = hash["ekmMeterReads"]["meter"]["read"]
#Save hash to bd
end
#def some_funct
end
end
I'm using Whenever for this. And this is how my schedule.rb looks like
set :output, "#{path}/log/cron.log"
every 1.minutes do
runner "Meter.all_read_data, :environment => 'development'
end
The problem is that actually only one Meter gets its data every minute and all the others are left without it. This is the content for my cron.log (it has an issue, but I have failed to find useful information about it.)
What am I missing? Thanks for any insight.
I got it. The url I'd been given to make the call was the wrong one.
Everything working smoothly now.
If you depend on someone else's information (like codes, keys, urls) be sure to be extra inquisitive about that information. Is better to lose some minutes doing that, than a day trying to do something which you are not supposed to do.

Associated model validations in Rails - and some questions of design

I've got a Task which has many LoggedTimes. I want to put a limit of 8 hours of logged time against a task on any given day. What is the best way to go about this so that I can check whether the person's latest logged time "takes their total over the limit" for a day, so to speak?
Here's where I am beginning (Task.rb):
validate :max_logged_daily_time
def max_logged_daily_time
if (params[:session_time] + (logged_times.where(:created_at => Date.today).to_a.sum(&:session_time)/60)) > 8
errors.add_to_base("Can't have more than 8 hours logged a day")
logged_time.errors.add('session_time', 'Logged times exceeded today')
end
end
Currently this validation is not working (adding another LoggedTime once 8 hours of previous logged times have been registered simply adds it to the rest, instead of throwing an error. Since no errors are being thrown, I'm struggling to pick my way through the problem. Is it something to do with the handling of params?
Which brings me to the question of design: in theory I could revise the view so that the user is only able to submit 8 hours minus the total amount of time they've logged that day; however this seems like a clunky solution and against the principle of keeping validations in the model. (And it certainly doesn't help me solve this model validations problem).
Any advice here?
TIA
class Task < ActiveRecord::Base
has_many :logged_times
def hours_today
LoggedTime.daily_hours_by_task(self).to_a.sum(&:session_time)
end
end
class LoggedTime < ActiveRecord::Base
belongs_to :task
scope :daily_hours_by_task, lambda { |task| task.\
logged_times.\
where('logged_times.created_at >= ? AND logged_times.created_at < ?',
Date.today, Date.today + 1) }
validate :max_logged_daily_time
private
def max_logged_daily_time
if task && ((task.hours_today + session_time) / 60.0) > 8
errors.add('session_time', 'Logged times exceeded today')
end
end
end
Some notes:
created_at is a DateTime, so you'll
need to test both the start and end
of the day
The validation also prevents the
addition of a single LoggedTime which by itself exceeds the maximum.
Dividing by an integer truncates and
will give the wrong result -- add the
.0 to convert to a float.
This only validates the LoggedTime,
not the Task, so you might want to
add validates_associated in the
Task model.
Validation is bypassed when Task is nil
EDIT
Well, hours_today should really be called minutes_today, but you get the idea.
I'd create a separate method for getting the sum of hours for a given day.
def total_hrs_logged_for_date(date)
#some code
end
Test that method to make sure it works.
Possibly also do the same for calculating the current logged time.
then use those those two in your custom validator
so this line
if (params[:session_time] + (logged_times.where(:created_at => Date.today).to_a.sum(&:session_time)/60)) > 8
becomes
if total_hrs_logged_for_date(Date.today) + current_time_being_logged > 8
At the very least it will help you narrow down which of these isn't working.
I also notice that you have "params[:session_time]"
I think this is in Task.rb which sounds like a model. Probably what you want is just "session_time" instead.
I ended up revising Zetetic's response slightly, because I couldn't get it to work as is.
In the end, this worked:
class Task < ActiveRecord::Base
has_many :logged_times
validates_associated :logged_times
def minutes_today
logged_times.where('created_at >= ? AND created_at < ?', Date.today, Date.today + 1)
end
end
and the LoggedTime model:
class LoggedTime < ActiveRecord::Base
belongs_to :task
validate :max_logged_daily_time
private
def max_logged_daily_time
if task && ((task.minutes_today + session_time) / 60.0) > 8
errors.add('session_time', 'Logged times exceeded today')
end
end
end
I'm not sure why the scope method baulked, but it did. Any hints Zetetic?

Resources